SQLite e Python: Guida Pratica con 3 Esercizi Reali per Gestire Database, Query e Transazioni

Cerca:

Generic selectors
Exact matches only
Search in title
Search in content
Post Type Selectors
integrare SQLite in Python

Tutti sanno scrivere due righe di codice per salvare un file di testo. Ma cosa succede quando i dati diventano mille, diecimila o un milione? Lì i file di testo non bastano più e i fogli Excel iniziano a crashare.

Se vuoi costruire qualcosa che stia in piedi davvero – che sia un gestionale per un negozio o un piccolo sistema finanziario – devi far parlare il tuo codice con un database.

Non serve installare server giganti per iniziare: Python ha già tutto quello che ti serve “sotto il cofano” con SQLite.

In questo articolo non faremo teoria astratta. Scriveremo codice mettendoci alla prova con tre scenari reali: gestire una biblioteca, analizzare le vendite di un e-commerce e, la parte più delicata, spostare denaro senza perderne per strada.

Apri il tuo editor, si parte.

Pubblicità

ESERCIZIO 1: Fondamenti – Creazione Database e Tabella

Livello: Principiante
Contesto : La vecchia biblioteca scolastica usa ancora schede cartacee e un file Excel condiviso che viene sovrascritto per errore ogni settimana. Il tuo compito? Portarli nel ventunesimo secolo creando una struttura dati rigida che impedisca di inserire libri senza titolo o autore.

Testo:
Crea un database SQLite chiamato biblioteca.db con una tabella libri che abbia le seguenti colonne:

  • id (chiave primaria, intero)

  • titolo (testo, non nullo)

  • autore (testo, non nullo)

  • anno (intero)

  • disponibile (booleano, default True)

Inserisci 3 libri di esempio e verifica l’inserimento leggendo tutti i record.

Soluzione

import sqlite3

# 1. Connessione al database (lo crea se non esiste)
conn = sqlite3.connect('biblioteca.db')
cursor = conn.cursor()

# 2. Creazione tabella
cursor.execute('''
CREATE TABLE IF NOT EXISTS libri (
    id INTEGER PRIMARY KEY AUTOINCREMENT,
    titolo TEXT NOT NULL,
    autore TEXT NOT NULL,
    anno INTEGER,
    disponibile BOOLEAN DEFAULT 1
)
''')

# 3. Inserimento dati di esempio
libri_di_esempio = [
    ('Il Nome della Rosa', 'Umberto Eco', 1980, 1),
    ('1984', 'George Orwell', 1949, 0),
    ('I Promessi Sposi', 'Alessandro Manzoni', 1827, 1)
]

cursor.executemany('''
INSERT INTO libri (titolo, autore, anno, disponibile) 
VALUES (?, ?, ?, ?)
''', libri_di_esempio)

# 4. Verifica lettura dati
cursor.execute('SELECT * FROM libri')
records = cursor.fetchall()

print("Libri nella biblioteca:")
for record in records:
    print(f"ID: {record[0]}, Titolo: {record[1]}, Autore: {record[2]}, Anno: {record[3]}, Disponibile: {record[4]}")

# 5. Salva le modifiche e chiudi
conn.commit()
conn.close()
Libri nella biblioteca:
ID: 1, Titolo: Il Nome della Rosa, Autore: Umberto Eco, Anno: 1980, Disponibile: 1
ID: 2, Titolo: 1984, Autore: George Orwell, Anno: 1949, Disponibile: 0
ID: 3, Titolo: I Promessi Sposi, Autore: Alessandro Manzoni, Anno: 1827, Disponibile: 1

💡 Osservazioni:

  • AUTOINCREMENT fa sì che l’ID venga generato automaticamente

  • ? come segnaposto previene attacchi SQL injection

  • commit() è fondamentale per salvare le modifiche permanenti

  • SQLite non ha un tipo booleano nativo: memorizza True come 1 e False come 0.

Domanda di riflessione: Perché usiamo ? come segnaposto invece di concatenare direttamente le stringhe?

ESERCIZIO 2: Query di Selezione Avanzata

Livello: Base
Contesto: Il tuo responsabile marketing ha bisogno di risposte, e le vuole subito. Non gli interessa l’elenco dei 10.000 prodotti; vuole sapere dove stiamo guadagnando (Elettronica) e dove stiamo perdendo tempo. Qui impariamo a trasformare i dati grezzi in informazioni usando le aggregazioni.

Testo:
Crea un database negozio.db con tabella prodotti (id, nome, categoria, prezzo, quantita). Popolalo con almeno 10 prodotti. Scrivi query per:

  1. Tutti i prodotti con prezzo > 50€

  2. Prodotti della categoria “Elettronica” ordinati per prezzo decrescente

  3. Conteggio prodotti per categoria

  4. Prezzo medio per categoria

Forse potrebbe interessarti anche:  I PRAGMA di SQLite: Il Pannello di Controllo Segreto che Trasforma un File .db in un Motore Enterprise

Soluzione

import sqlite3

conn = sqlite3.connect('negozio.db')
cursor = conn.cursor()

# Creazione e popolamento tabella
cursor.execute('''
CREATE TABLE IF NOT EXISTS prodotti (
    id INTEGER PRIMARY KEY,
    nome TEXT,
    categoria TEXT,
    prezzo REAL,
    quantita INTEGER
)
''')

# Dati di esempio
prodotti = [
    (1, 'Smartphone', 'Elettronica', 599.99, 10),
    (2, 'T-shirt', 'Abbigliamento', 19.99, 50),
    (3, 'Laptop', 'Elettronica', 899.99, 5),
    (4, 'Jeans', 'Abbigliamento', 49.99, 30),
    (5, 'Cuffie', 'Elettronica', 79.99, 15),
    (6, 'Libro', 'Cancelleria', 14.99, 100),
    (7, 'Monitor', 'Elettronica', 249.99, 8),
    (8, 'Penna', 'Cancelleria', 1.99, 200),
    (9, 'Maglione', 'Abbigliamento', 39.99, 25),
    (10, 'Tablet', 'Elettronica', 349.99, 12)
]

cursor.executemany('INSERT INTO prodotti VALUES (?, ?, ?, ?, ?)', prodotti)

# Query 1: Prodotti costosi
print("1. Prodotti sopra 50€:")
cursor.execute('SELECT nome, prezzo FROM prodotti WHERE prezzo > 50 ORDER BY prezzo DESC')
for row in cursor.fetchall():
    print(f"- {row[0]}: {row[1]}€")

# Query 2: Elettronica ordinata
print("\n2. Elettronica per prezzo decrescente:")
cursor.execute('SELECT nome, prezzo FROM prodotti WHERE categoria = "Elettronica" ORDER BY prezzo DESC')
for row in cursor.fetchall():
    print(f"- {row[0]}: {row[1]}€")

# Query 3: Conteggio per categoria
print("\n3. Numero prodotti per categoria:")
cursor.execute('SELECT categoria, COUNT(*) FROM prodotti GROUP BY categoria')
for row in cursor.fetchall():
    print(f"- {row[0]}: {row[1]} prodotti")

# Query 4: Prezzo medio
print("\n4. Prezzo medio per categoria:")
cursor.execute('SELECT categoria, AVG(prezzo) FROM prodotti GROUP BY categoria')
for row in cursor.fetchall():
    print(f"- {row[0]}: {row[1]:.2f}€")

conn.commit()
conn.close()
1. Prodotti sopra 50€:
- Laptop: 899.99€
- Smartphone: 599.99€
- Tablet: 349.99€
- Monitor: 249.99€
- Cuffie: 79.99€

2. Elettronica per prezzo decrescente:
- Laptop: 899.99€
- Smartphone: 599.99€
- Tablet: 349.99€
- Monitor: 249.99€
- Cuffie: 79.99€

3. Numero prodotti per categoria:
- Abbigliamento: 3 prodotti
- Cancelleria: 2 prodotti
- Elettronica: 5 prodotti

4. Prezzo medio per categoria:
- Abbigliamento: 36.66€
- Cancelleria: 8.49€
- Elettronica: 435.99€

💡 Osservazioni:

  • GROUP BY raggruppa i risultati per valori di colonna

  • AVG(), COUNT() sono funzioni di aggregazione

  • ORDER BY con DESC ordina in modo decrescente

Domanda di riflessione: Cosa succede se usiamo GROUP BY senza funzioni di aggregazione?

ESERCIZIO 3: Transazioni e Gestione Errori

Livello: Intermedio
Contesto: Scrivere software finanziario è diverso dal fare un sito web. Se un’immagine non carica, l’utente aggiorna la pagina. Se un bonifico si blocca a metà, hai perso i soldi del cliente e la tua reputazione. Qui vediamo come le transazioni atomiche sono l’unica barriera tra un sistema affidabile e un disastro legale.

Due operazioni devono essere eseguite insieme: prelievo da un conto e deposito in un altro (bonifico).

Testo:
Crea un database banca.db con tabella conti (numero_conto, intestatario, saldo). Implementa una funzione trasferisci() che esegue un trasferimento tra due conti usando una transazione. Gestisci gli errori (es. saldo insufficiente).

Soluzione

import sqlite3

def setup_database():
    conn = sqlite3.connect('banca.db')
    cursor = conn.cursor()
    
    cursor.execute('''
    CREATE TABLE IF NOT EXISTS conti (
        numero_conto TEXT PRIMARY KEY,
        intestatario TEXT,
        saldo REAL DEFAULT 0
    )
    ''')
    
    # Reset e popolamento
    cursor.execute('DELETE FROM conti')
    cursor.executemany(
        'INSERT INTO conti (numero_conto, intestatario, saldo) VALUES (?, ?, ?)',
        [
            ('IT001', 'Mario Rossi', 1000.00),
            ('IT002', 'Luigi Verdi', 500.00),
            ('IT003', 'Anna Bianchi', 2500.00)
        ]
    )
    
    conn.commit()
    conn.close()

def trasferisci(da_conto, a_conto, importo):
    conn = sqlite3.connect('banca.db')
    cursor = conn.cursor()
    
    try:
        # Inizia transazione (implicita con BEGIN)
        conn.execute('BEGIN TRANSACTION')
        
        # Verifica saldo sufficiente
        cursor.execute('SELECT saldo FROM conti WHERE numero_conto = ?', (da_conto,))
        saldo_attuale = cursor.fetchone()[0]
        
        if saldo_attuale < importo:
            raise ValueError("Saldo insufficiente")
        
        # Prelievo dal conto sorgente
        cursor.execute(
            'UPDATE conti SET saldo = saldo - ? WHERE numero_conto = ?',
            (importo, da_conto)
        )
        
        # Deposito sul conto destinazione
        cursor.execute(
            'UPDATE conti SET saldo = saldo + ? WHERE numero_conto = ?',
            (importo, a_conto)
        )
        
        # Commit della transazione
        conn.commit()
        print(f"Trasferimento di {importo}€ da {da_conto} a {a_conto} completato.")
        
    except Exception as e:
        # Rollback in caso di errore
        conn.rollback()
        print(f"Errore durante il trasferimento: {e}")
        
    finally:
        # Mostra saldi aggiornati
        cursor.execute('SELECT numero_conto, intestatario, saldo FROM conti')
        print("\nStato conti:")
        for row in cursor.fetchall():
            print(f"- {row[0]} ({row[1]}): {row[2]:.2f}€")
        
        conn.close()

# Setup e test
setup_database()
print("=== TRASFERIMENTO 1: Successo ===")
trasferisci('IT001', 'IT002', 200.00)

print("\n=== TRASFERIMENTO 2: Saldo insufficiente ===")
trasferisci('IT002', 'IT001', 1000.00)

 

=== TRASFERIMENTO 1: Successo ===
Trasferimento di 200.0€ da IT001 a IT002 completato.

Stato conti:
- IT001 (Mario Rossi): 800.00€
- IT002 (Luigi Verdi): 700.00€
- IT003 (Anna Bianchi): 2500.00€

=== TRASFERIMENTO 2: Saldo insufficiente ===
Errore durante il trasferimento: Saldo insufficiente

Stato conti:
- IT001 (Mario Rossi): 800.00€
- IT002 (Luigi Verdi): 700.00€
- IT003 (Anna Bianchi): 2500.00€

💡 Osservazioni:

  • Le transazioni garantiscono ACID (Atomicità, Consistency, Isolation, Durability)

Forse potrebbe interessarti anche:  Progettare Database Relazionali: La Guida Pratica dall'ERD all'Integrità dei Dati

Perché servono davvero le transazioni

Le transazioni sono il paracadute invisibile di ogni sistema finanziario. Non servono quando tutto va bene: servono quando qualcosa va storto.

Garantiscono le proprietà ACID, il fondamento di qualsiasi software che muove denaro:

  • Atomicità — o tutto o niente. Se il prelievo riesce ma il deposito fallisce, l’operazione viene annullata come se non fosse mai esistita.
  • Consistency — il database rimane valido. Nessun conto va in negativo senza motivo, nessun saldo “magico” compare o scompare.
  • Isolation — due bonifici simultanei non si pestano i piedi. Ogni transazione vede un mondo coerente, anche se altre stanno lavorando in parallelo.
  • Durability — una volta confermata, l’operazione non si perde. Anche se il server esplode un secondo dopo il commit, il bonifico rimane valido.

Senza transazioni, un errore tra prelievo e deposito può far sparire denaro dal sistema. È il confine netto tra un software amatoriale e un sistema che può gestire soldi veri senza tremare.

  • BEGIN TRANSACTION inizia, COMMIT conferma, ROLLBACK annulla

  • Il try...except...finally gestisce sia errori applicativi che di database

Domanda di riflessione: Perché è importante usare le transazioni in operazioni finanziarie?

RISPOSTE ALLE DOMANDE DI RIFLESSIONE

Esercizio 1

Perché usiamo ? come segnaposto invece di concatenare direttamente le stringhe?
Usare ? come segnaposto (parameterized queries) previene attacchi di SQL injection. Se concatenassimo direttamente stringhe con input utente, un malintenzionato potrebbe inserire codice SQL maligno. Ad esempio, invece di un nome, potrebbe inserire "'; DROP TABLE libri; --". Con i segnaposti, i dati sono trattati sempre come valori, non come parte del codice SQL. Inoltre, migliora le prestazioni perché SQLite può riutilizzare il piano di esecuzione per query simili.

Esercizio 2

Cosa succede se usiamo GROUP BY senza funzioni di aggregazione?
Se usiamo GROUP BY senza funzioni di aggregazione, otteniamo solo una riga per ogni gruppo distinto. In SQL standard, questo causerebbe errore, ma in SQLite (e MySQL) viene restituita una riga arbitraria per gruppo (di solito la prima trovata). Questa è considerata una cattiva pratica perché il risultato non è deterministico. Dovremmo sempre usare GROUP BY con funzioni di aggregazione (COUNT, SUM, AVG, ecc.) o selezionare solo le colonne raggruppate.

Forse potrebbe interessarti anche:  Python per Data Engineering: 3 esercizi reali su Excel, API e automazione aziendale

Esercizio 3

Perché è importante usare le transazioni in operazioni finanziarie?
Le transazioni garantiscono la proprietà ACID:

  1. Atomicità: O tutte le operazioni hanno successo o nessuna (evita trasferimenti “a metà”)

  2. Consistency: Il database rimane in stato consistente (saldo totale preservato)

  3. Isolation: Transazioni concorrenti non interferiscono tra loro

  4. Durability: Una volta confermata, la transazione è permanente

Senza transazioni, in caso di errore dopo il prelievo ma prima del deposito, il denaro potrebbe “scomparire” dal sistema.

 

Analisi Applicativa: Perché questi esercizi sono interessanti?

Analisi Esercizio 1: La Base (Struttura)

  • Questo esercizio insegna la disciplina. Molti principianti tentano di gestire i dati con liste di liste o dizionari in Python.

  • Introduce il concetto di schema. Obbligare un campo a essere NOT NULL o avere un DEFAULT sposta la responsabilità della validazione dal codice Python al motore del database. Questo rende l’applicazione più robusta: non puoi “dimenticarti” di inserire un autore, il database ti bloccherà.

Analisi Esercizio 2: L’Intelligence (Estrazione)

  •  Si passa dal semplice immagazzinamento all’analisi. La query n.4 (AVG) è la più potente qui.

  • Nel mondo reale, i dati grezzi sono rumore. Questo esercizio simula la creazione di dashboard. Se stai costruendo un backend per un’app di gestione spese, non mostri all’utente tutte le 500 transazioni; mostri “Totale speso in Cibo: 400€”. Qui si impara che il calcolo lo deve fare il DB, non Python (che sarebbe più lento a scaricare tutti i dati e sommarli in un ciclo for).

Analisi Esercizio 3: La Sicurezza (Integrità)

  • È l’esercizio spartiacque tra un programmatore amatoriale e un professionista.

  • Introduce il concetto di ACID. Senza questo pattern, in un e-commerce reale, se il server crasha dopo aver scalato i soldi dalla carta di credito ma prima di aver confermato l’ordine, il cliente ha pagato per nulla. Questo esercizio dimostra come rendere le operazioni reversibili in caso di errore. È la base di qualsiasi sistema affidabile.

 

Pubblicità