- Pubblicato il
Cos’è SQL? Capirne scopo, origini e utilizzo
- Autori
Cos’è SQL? Capirne scopo, origini e utilizzo
La maggior parte delle applicazioni moderne, dai semplici blog agli enormi social network, archivia e recupera dati usando un linguaggio chiamato SQL.
In questo articolo scoprirai:
- Cos’è SQL e perché è stato creato
- Come i database relazionali organizzano le informazioni
- Un esempio concreto con due tabelle
- Come eseguire vere query SQL, compresi i
JOIN - Perché SQL è ancora lo standard dopo oltre 50 anni
Iniziamo.
Cos’è SQL?
SQL (Structured Query Language) è un linguaggio di programmazione dichiarativo creato per gestire i database relazionali, database costruiti attorno a tabelle, righe e relazioni tra esse.
SQL permette di:
- Creare tabelle
- Inserire e modificare dati
- Interrogare i dati usando filtri
- Unire dati correlati da più tabelle
- Imporre vincoli come email univoche o chiavi esterne
SQL viene usato in molti sistemi di database relazionali popolari. Ecco alcuni esempi:
- PostgreSQL: database relazionale open source avanzato con un forte supporto agli standard SQL.
- MySQL: ampiamente usato nelle applicazioni web, noto per velocità e semplicità.
- MariaDB: fork di MySQL guidato dalla community, con funzionalità migliorate per le prestazioni.
- SQLite: database leggero basato su file, usato in app mobili, browser e sistemi embedded.
- Microsoft SQL Server: database commerciale di livello aziendale con strumenti solidi negli ambienti Windows e cloud.
- Servizi SQL cloud completamente gestiti come:
- Amazon RDS
- Google Cloud SQL
- PlanetScale
Queste piattaforme (Amazon RDS, Google CLoud SQL, PlanetScale) gestiscono backup, scalabilità, aggiornamenti e manutenzione, lasciandoti comunque la potenza di SQL.
Perché è stato creato SQL
Negli anni Settanta, presso IBM, i ricercatori Edgar F. Codd e Donald Chamberlin lavoravano a un modo migliore per archiviare e recuperare dati. All’epoca, i database erano gerarchici o basati su reti e interagire con essi era:
- Complesso
- Soggetto a errori
- Strettamente legato all’archiviazione fisica
- Difficile da interrogare in modo flessibile
Per risolvere il problema, introdussero il modello relazionale, che organizza i dati in tabelle con relazioni ben definite.
SQL è stato creato perché:
- Gli sviluppatori avevano bisogno di un linguaggio semplice e leggibile per interrogare i dati.
- Le aziende avevano bisogno di regole coerenti per archiviare le relazioni.
- Era necessario accedere ai dati senza conoscere i dettagli interni dello storage.
Il risultato?
SQL è diventato lo standard nel 1986 (ANSI SQL) e ancora oggi quasi tutti i principali database sono costruiti attorno a esso.
Comprendere tabelle, colonne e relazioni
In un database relazionale:
- Una tabella è come un foglio di calcolo (righe + colonne)
- Una riga rappresenta un singolo record
- Una colonna rappresenta una proprietà
- Una chiave primaria identifica in modo univoco ogni riga
- Una chiave esterna collega una tabella a un’altra
Vediamo un semplice esempio con utenti e ordini.
Schema di esempio: utenti e ordini
Tabella 1: users
| id | name | |
|---|---|---|
| 1 | Alice | alice@example.com |
| 2 | Bob | bob@example.com |
| 3 | Caroline | caroline@example.com |
Tabella 2: orders
| id | user_id | product | price |
|---|---|---|---|
| 1 | 1 | Coffee Mug | 12.99 |
| 2 | 1 | Hoodie | 39.99 |
| 3 | 2 | Stickers | 4.99 |
Relazione
- Ogni ordine appartiene a un utente (
orders.user_id → users.id). - Un utente può avere più ordini (relazione uno-a-molti).
Creare le tabelle (esempio SQL)
CREATE TABLE users (
id SERIAL PRIMARY KEY,
name TEXT NOT NULL,
email TEXT UNIQUE NOT NULL
);
CREATE TABLE orders (
id SERIAL PRIMARY KEY,
user_id INTEGER NOT NULL REFERENCES users(id),
product TEXT NOT NULL,
price NUMERIC(10,2) NOT NULL
);
Questo crea due tabelle relazionali fortemente tipizzate con un vincolo di chiave esterna. Ma cosa significa “fortemente tipizzate”? Nei database SQL come PostgreSQL e MySQL, ogni colonna ha un tipo di dato rigoroso:
- Le stringhe devono essere stringhe
- I numeri devono essere numeri
- Le date devono essere date
- I tipi errati causano errori
Questo previene errori come:
- Salvare “venti dollari” in una colonna numerica
- Salvare 123 in un campo email
- Salvare date non valide
SQLite è diverso: è flessibile nei tipi (“tipizzazione dinamica”), cioè impone i tipi in modo meno rigoroso. PostgreSQL è il più fortemente tipizzato tra i principali database.
Creazione delle tabelle spiegata riga per riga
Tabella 1: users
CREATE TABLE users (
Spiegazione: Avvia la creazione di una nuova tabella chiamata users.
- Funziona in: PostgreSQL, MySQL, MariaDB, SQLite, SQL Server
- “Tabella” = insieme strutturato di righe e colonne nel database.
id SERIAL PRIMARY KEY,
Spiegazione:
id → Il nome della colonna.
SERIAL → Intero a incremento automatico. Specifico di PostgreSQL.
Equivalente in altri motori:
- MySQL:
INT AUTO_INCREMENT - SQLite:
INTEGER PRIMARY KEY AUTOINCREMENT
- MySQL:
PRIMARY KEY → Garantisce l’unicità e identifica ogni riga.
name TEXT NOT NULL,
Spiegazione:
- name → Colonna per archiviare testo.
- TEXT → Testo di lunghezza variabile (stringa).
- NOT NULL → Non può essere vuota.
Funziona in: PostgreSQL, MySQL, MariaDB, SQLite (ma SQL Server usa VARCHAR(MAX) invece di TEXT).
email TEXT UNIQUE NOT NULL
Spiegazione:
- email → Archivia l’indirizzo email dell’utente.
- TEXT → Tipo testuale.
- UNIQUE → Impedisce email duplicate.
- NOT NULL → Campo obbligatorio.
Funziona in: PostgreSQL, MySQL, MariaDB, SQLite (tenendo presenti le differenze interne del tipo TEXT).
);
Spiegazione: Chiude la definizione della tabella.
Tabella 2: orders
CREATE TABLE orders (
Avvia una nuova tabella chiamata orders. Funziona in tutti i principali motori.
id SERIAL PRIMARY KEY,
Stesso significato che nella tabella users:
- Chiave primaria a incremento automatico
- Tipo specifico di PostgreSQL (
SERIAL) - MySQL usa
INT AUTO_INCREMENT - SQLite usa
INTEGER PRIMARY KEY AUTOINCREMENT
user_id INTEGER NOT NULL REFERENCES users(id),
Spiegazione:
- user_id → Un numero che collega l’ordine a una riga di
users. - INTEGER → Numero intero.
- NOT NULL → Ogni ordine deve appartenere a un utente.
- REFERENCES users(id) → Vincolo di chiave esterna.
Significato di chiave esterna:
- Garantisce che
user_idcorrisponda a un veroidnella tabellausers. - Se un utente viene eliminato, il database farà rispettare la relazione secondo le regole della chiave esterna.
Compatibilità dei motori:
- PostgreSQL: supporto completo.
- MySQL/MariaDB: funziona, ma richiede il motore di storage = InnoDB.
- SQLite: supportato solo quando il supporto delle chiavi esterne è abilitato (
PRAGMA foreign_keys = ON;).
product TEXT NOT NULL,
Archivia il nome del prodotto acquistato.
- Tipo TEXT → Funziona diversamente tra i motori, ma archivia sempre stringhe.
- Obbligatorio (
NOT NULL).
price NUMERIC(10,2) NOT NULL
Spiegazione:
price → Il costo del prodotto.
NUMERIC(10,2) → Numero a precisione fissa con:
- fino a 10 cifre totali
- 2 cifre dopo il separatore decimale
È ideale per il denaro perché evita gli errori in virgola mobile.
Compatibilità dei motori:
- PostgreSQL: supporto perfetto.
- MySQL:
DECIMAL(10,2)è equivalente. - SQLite: lo accetta ma archivia internamente come TEXT, INT o REAL (tipizzazione dinamica).
);
Termina la tabella.
Inserire i dati di esempio
Ora che le tabelle sono state create, inseriamo i dati di esempio mostrati prima. Non forniremo manualmente alcun valore id, il database li genererà automaticamente usando il tipo SERIAL (a incremento automatico).
Inserire i dati di users
INSERT INTO users (name, email)
VALUES
('Alice', 'alice@example.com'),
('Bob', 'bob@example.com'),
('Caroline', 'caroline@example.com');
Inserimento in users spiegato riga per riga
INSERT INTO users (name, email)
INSERT INTO usersStiamo aggiungendo nuove righe alla tabella users.(name, email)Forniamo solonameeemail, nonid.Perché non fornire l’ID? Poiché la colonna
idusa un tipo seriale/a incremento automatico (SERIALdi PostgreSQL), il database creerà automaticamente gli ID:Primo utente →
id = 1Secondo utente →
id = 2Terzo utente →
id = 3
Questo è prevedibile in PostgreSQL, MySQL, MariaDB e SQLite (usando i rispettivi meccanismi di incremento automatico).
VALUES
('Alice', 'alice@example.com'),
('Bob', 'bob@example.com'),
('Caroline', 'caroline@example.com');
VALUESintroduce un elenco di righe da inserire.- Ogni gruppo
( ... )corrisponde a una nuova riga. - Il database assegnerà automaticamente gli ID nell’ordine in cui appaiono queste righe.
Tabella users risultante dopo l’inserimento:
| id | name | |
|---|---|---|
| 1 | Alice | alice@example.com |
| 2 | Bob | bob@example.com |
| 3 | Caroline | caroline@example.com |
Anche se non abbiamo scritto manualmente alcun ID, il meccanismo di incremento automatico garantisce questo ordine.
Inserire i dati di orders
Per inserire orders, dobbiamo fornire user_id, perché un ordine deve appartenere a un utente specifico.
Anche se non abbiamo impostato manualmente gli ID utente, sappiamo comunque quali sono:
- Alice → ID 1
- Bob → ID 2
- Caroline → ID 3
Sono stati generati automaticamente nell’ordine di inserimento e l’incremento automatico assegna sempre gli ID in sequenza, a meno che delle righe vengano eliminate.
Ecco l’inserimento:
INSERT INTO orders (user_id, product, price)
VALUES
(1, 'Coffee Mug', 12.99),
(1, 'Hoodie', 39.99),
(2, 'Stickers', 4.99);
Inserimento in orders spiegato riga per riga
INSERT INTO orders (user_id, product, price)
Stiamo inserendo nuove righe nella tabella orders.
Specifichiamo:
user_id→ A quale utente appartiene l’ordineproduct→ Nomeprice→ Costo
Nota ancora: non forniamo id (orders.id), perché si incrementa automaticamente.
VALUES
(1, 'Coffee Mug', 12.99),
(1, 'Hoodie', 39.99),
(2, 'Stickers', 4.99);
(1, 'Coffee Mug', 12.99)- Questo ordine appartiene all’utente 1 (Alice).
(1, 'Hoodie', 39.99)- Un altro ordine per Alice.
(2, 'Stickers', 4.99)- Ordine per l’utente 2 (Bob).
Come sappiamo quale ID ha ogni utente? Perché:
Gli utenti sono stati inseriti per primi.
Gli ID a incremento automatico assegnano sempre interi sequenziali.
Quindi:
- Primo utente inserito → ID 1
- Secondo utente inserito → ID 2
- Terzo utente inserito → ID 3
Questo rende del tutto prevedibile il riferimento agli utenti tramite ID.
Tabelle finali risultanti
users
| id | name | |
|---|---|---|
| 1 | Alice | alice@example.com |
| 2 | Bob | bob@example.com |
| 3 | Caroline | caroline@example.com |
orders
| id | user_id | product | price |
|---|---|---|---|
| 1 | 1 | Coffee Mug | 12.99 |
| 2 | 1 | Hoodie | 39.99 |
| 3 | 2 | Stickers | 4.99 |
Interrogare i dati con SQL
Ecco alcune query SQL che possiamo eseguire sulle due tabelle definite prima.
1. Selezionare tutti gli utenti
SELECT * FROM users;
Risultato:
| id | name | |
|---|---|---|
| 1 | Alice | alice@example.com |
| 2 | Bob | bob@example.com |
| 3 | Caroline | caroline@example.com |
2. Selezionare gli utenti con un filtro
SELECT * FROM users
WHERE email LIKE '%alice@example.com';
Cosa significa:
LIKEè un operatore SQL usato per confrontare testo tramite pattern.%è un carattere jolly che corrisponde a qualsiasi numero di caratteri, anche zero.
Quindi:
'%alice@example.com'significa: “Trova qualsiasi testo che termini conalice@example.com.”
Questo restituirà la riga in cui l’email dell’utente è esattamente:
alice@example.com
ma corrisponderebbe anche a valori come:
test-alice@example.comhello.alice@example.comanythingalice@example.com
se esistessero.
Risultato:
| id | name | |
|---|---|---|
| 1 | Alice | alice@example.com |
Questa query restituisce solo l’utente la cui email corrisponde al filtro.
3. Inserire un nuovo ordine
INSERT INTO orders (user_id, product, price)
VALUES (2, 'T-Shirt', 29.99);
Tabella orders risultante dopo l’inserimento:
| id | user_id | product | price |
|---|---|---|---|
| 1 | 1 | Coffee Mug | 12.99 |
| 2 | 1 | Hoodie | 39.99 |
| 3 | 2 | Stickers | 4.99 |
| 4 | 2 | T-Shirt | 29.99 |
- La nuova riga ha
id = 4, generato automaticamente.user_id = 2collega questo ordine a Bob.
JOIN: una parte potente di SQL
I JOIN permettono di interrogare dati correlati tra più tabelle.
Esploriamoli usando le nostre tabelle users e orders.
Esempio 1: INNER JOIN (il più comune)
Ottieni ogni ordine insieme all’utente che lo ha effettuato.
SELECT users.name, orders.product, orders.price
FROM orders
INNER JOIN users
ON orders.user_id = users.id;
Risultato:
| name | product | price |
|---|---|---|
| Alice | Coffee Mug | 12.99 |
| Alice | Hoodie | 39.99 |
| Bob | Stickers | 4.99 |
| Bob | T-Shirt | 29.99 |
Bob ora appare due volte, a causa del nuovo ordine inserito prima:
INSERT INTO orders (user_id, product, price)
VALUES (2, 'T-Shirt', 29.99);
Esempio 2: LEFT JOIN
Ottieni tutti gli utenti, anche quelli senza ordini.
SELECT users.name, orders.product
FROM users
LEFT JOIN orders
ON users.id = orders.user_id;
Risultato:
| name | product |
|---|---|
| Alice | Coffee Mug |
| Alice | Hoodie |
| Bob | Stickers |
| Bob | T-Shirt |
| Caroline | null |
Caroline appare con null perché non ha ordini.
Esempio 3: query aggregata
Totale del denaro speso da ciascun utente:
SELECT users.name, SUM(orders.price) AS total_spent
FROM users
LEFT JOIN orders ON users.id = orders.user_id
GROUP BY users.name;
Risultato:
| name | total_spent |
|---|---|
| Alice | 52.98 |
| Bob | 34.98 |
| Caroline | null |
Spiegazione
- Alice: 12.99 + 39.99 = 52.98
- Bob: 4.99 + 29.99 = 34.98
- Caroline: nessun ordine → NULL
Dove approfondire
- Documentazione di PostgreSQL
- Manuale di riferimento di MySQL
- Tutorial SQL di W3Schools
- Documentazione di SQLite
Considerazioni finali
SQL esiste da oltre 50 anni, ma continua ad alimentare le applicazioni più importanti al mondo, perché è:
- Dichiarativo
- Coerente
- Prevedibile
- Scalabile
- Sostenuto da decenni di ricerca
Una volta compreso come funzionano tabelle e join, SQL diventa uno degli strumenti più potenti nel tuo repertorio di programmazione.