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é:

    1. Gli sviluppatori avevano bisogno di un linguaggio semplice e leggibile per interrogare i dati.
    2. Le aziende avevano bisogno di regole coerenti per archiviare le relazioni.
    3. 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

    Tabella 2: orders

    iduser_idproductprice
    11Coffee Mug12.99
    21Hoodie39.99
    32Stickers4.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
    • 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_id corrisponda a un vero id nella tabella users.
    • 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 users Stiamo aggiungendo nuove righe alla tabella users.

    • (name, email) Forniamo solo name e email, non id.

      Perché non fornire l’ID? Poiché la colonna id usa un tipo seriale/a incremento automatico (SERIAL di PostgreSQL), il database creerà automaticamente gli ID:

    • Primo utente → id = 1

    • Secondo utente → id = 2

    • Terzo 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');
    
    • VALUES introduce 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:

    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’ordine
      • product → Nome
      • price → 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é:

    1. Gli utenti sono stati inseriti per primi.

    2. Gli ID a incremento automatico assegnano sempre interi sequenziali.

    3. 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

    orders

    iduser_idproductprice
    11Coffee Mug12.99
    21Hoodie39.99
    32Stickers4.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:

    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 con alice@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.com
    • hello.alice@example.com
    • anythingalice@example.com

    se esistessero.

    Risultato:

    idnameemail
    1Alicealice@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:

    iduser_idproductprice
    11Coffee Mug12.99
    21Hoodie39.99
    32Stickers4.99
    42T-Shirt29.99
    • La nuova riga ha id = 4, generato automaticamente.
    • user_id = 2 collega 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:

    nameproductprice
    AliceCoffee Mug12.99
    AliceHoodie39.99
    BobStickers4.99
    BobT-Shirt29.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:

    nameproduct
    AliceCoffee Mug
    AliceHoodie
    BobStickers
    BobT-Shirt
    Carolinenull

    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:

    nametotal_spent
    Alice52.98
    Bob34.98
    Carolinenull

    Spiegazione

    • Alice: 12.99 + 39.99 = 52.98
    • Bob: 4.99 + 29.99 = 34.98
    • Caroline: nessun ordine → NULL

    Dove approfondire

    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.