Spesso i database vengono trattati come semplici archivi a cui delegare banali operazioni di filtro o aggregazione, lasciando l’onere del calcolo scientifico e analitico a linguaggi esterni. Eppure, spostare l’elaborazione matematica direttamente dove risiede il dato grezzo è una delle strategie più efficaci per abbattere la latenza di rete e semplificare l’architettura.
Questi 3 esercizi pratici mostrano come trasformare T-SQL in un vero e proprio motore analitico utilizzando le funzioni matematiche native (LOG, EXP, POWER, SQRT).
Ogni scenario affronta un problema reale ed è scritto con una logica production-ready: gestione preventiva degli errori, robustezza del codice e attenzione alle performance, requisiti indispensabili quando si coordinano progetti di analisi dati complessi e automatizzati.
Esercizio 1 – Calcolo del pH di una soluzione (facile)
Testo
In un laboratorio chimico, la concentrazione di ioni idrogeno [math][H^+][/math] (in mol/L) viene misurata per diverse soluzioni. Il pH è definito come:
[math]\text{pH} = -\log_{10}([H^+])[/math]
Scrivi una query T‑SQL che, data una tabella Soluzioni con colonna ConcentrazioneH (tipo DECIMAL(10,8)), restituisca per ogni soluzione il valore del pH arrotondato a 2 decimali. Gestisci i casi in cui la concentrazione è ≤ 0 (valore non valido) restituendo NULL.
Soluzione
-- Tabella di esempio
CREATE TABLE #Soluzioni (
Id INT IDENTITY(1,1),
ConcentrazioneH DECIMAL(10,8)
);
INSERT INTO #Soluzioni (ConcentrazioneH) VALUES
(0.00000010), -- pH ≈ 7.00
(0.00000001), -- pH ≈ 8.00
(0.00000100), -- pH ≈ 6.00
(-0.00000005), -- non valido
(0.0); -- non valido
-- Query principale
SELECT
Id,
ConcentrazioneH,
-- LOG10 calcola il logaritmo in base 10.
-- Se il valore è <= 0, LOG10 genera un errore; usiamo NULLIF per evitarlo. CASE WHEN ConcentrazioneH > 0
THEN ROUND(-LOG10(ConcentrazioneH), 2)
ELSE NULL
END AS pH
FROM #Soluzioni;
-- Pulizia
DROP TABLE #Soluzioni;
Spiegazione
- Funzione LOG10: calcola il logaritmo in base 10 di un numero positivo.
- Controllo di validità: con CASE verifichiamo che la concentrazione sia strettamente positiva; altrimenti restituiamo NULL per evitare errori di runtime.
- Arrotondamento: ROUND(…, 2) garantisce due decimali, come richiesto.
- Production‑ready: la gestione degli errori (con NULLIF o CASE) è essenziale in ambienti reali.
💡 Osservazione strategica
In T‑SQL, le funzioni matematiche generano eccezioni se chiamate con argomenti fuori dominio. Utilizzare CASE o IIF per pre‑validare i dati evita interruzioni della query e migliora la robustezza.
Domanda di riflessione
Quale proprietà dei logaritmi hai utilizzato per definire il pH? E cosa succederebbe se la base del logaritmo fosse 2 invece di 10?
Esercizio 2 – Crescita esponenziale di una popolazione (facile‑medio)
Testo
La popolazione di una colonia batterica cresce secondo il modello esponenziale:
[math]N(t) = N_0 \cdot e^{rt}[/math]
dove [math]N_0[/math] è la popolazione iniziale, [math]r[/math] è il tasso di crescita (per ora) e [math]t[/math] è il tempo in ore.
Data una tabella Colonie con colonne Nome, PopIniziale e TassoCrescita, scrivi una query che calcoli la popolazione prevista dopo 8 ore. Usa la funzione EXP di T‑SQL. Gestisci eventuali valori negativi di TassoCrescita (decrescita) e arrotonda il risultato all’intero più vicino.
Soluzione
CREATE TABLE #Colonie (
Nome VARCHAR(50),
PopIniziale INT,
TassoCrescita DECIMAL(5,4) -- es. 0.023 per 2.3% all'ora
);
INSERT INTO #Colonie (Nome, PopIniziale, TassoCrescita) VALUES
('E. coli', 1000, 0.023),
('Lactobacillus', 500, -0.005), -- decrescita
('S. aureus', 2000, 0.000);
DECLARE @Ore INT = 8;
SELECT
Nome,
PopIniziale,
TassoCrescita,
-- EXP(x) calcola e^x.
-- Moltiplichiamo PopIniziale * EXP(TassoCrescita * @Ore)
-- Arrotondiamo con ROUND(..., 0) e convertiamo a INT.
CAST(ROUND(PopIniziale * EXP(TassoCrescita * @Ore), 0) AS INT) AS PopolazionePrevista
FROM #Colonie;
DROP TABLE #Colonie;
Spiegazione
- EXP è la funzione esponenziale naturale ([math]e^x[/math]).
- Il prodotto
TassoCrescita * @Oreè l’esponente; se negativo, EXP restituisce un valore compreso tra 0 e 1, modellando la decrescita. - Arrotondamento: ROUND(…, 0) arrotonda al numero intero più vicino; il cast a INT elimina i decimali (già arrotondati).
- Gestione di tasso zero: EXP(0)=1, quindi la popolazione rimane invariata.
💡 Osservazione
La funzione EXP è monotona crescente e accetta qualsiasi numero reale; non necessita di controlli di dominio (a differenza di LOG). È utile per modellare crescita/decadimento esponenziale.
Domanda di riflessione
Perché usiamo EXP invece di POWER con base fissa? Quale vantaggio offre la base [math]e[/math] in contesti di crescita continua?
Esercizio 3 – Tempo di raddoppio di un investimento (medio)
Testo
Un consulente finanziario deve calcolare il numero di anni necessari affinché un investimento raddoppi il proprio valore, dato un tasso di interesse annuale composto continuamente. La formula è:
[math]t = \frac{\ln(2)}{r}[/math]
dove [math]r[/math] è il tasso di interesse annuale (es. 0.05 per il 5%).
Scrivi una stored procedure CalcolaTempoRaddoppio che accetta il tasso [math]@r[/math] (tipo DECIMAL(10,6)) e restituisce il tempo in anni, arrotondato a 2 decimali. Gestisci i casi in cui [math]@r[/math] sia ≤ 0 (tasso non valido) restituendo un messaggio di errore.
La procedura deve essere production‑ready, con gestione delle eccezioni e commenti.
Soluzione
CREATE PROCEDURE CalcolaTempoRaddoppio
@r DECIMAL(10,6),
@tempo DECIMAL(10,2) OUTPUT
AS
BEGIN
SET NOCOUNT ON;
BEGIN TRY
-- Controllo validità del tasso
IF @r <= 0
BEGIN
RAISERROR('Il tasso di interesse deve essere positivo.', 16, 1);
RETURN;
END
-- Calcolo: ln(2) / r. LOG(2) in T-SQL è il logaritmo naturale (base e).
SET @tempo = ROUND(LOG(2) / @r, 2);
END TRY
BEGIN CATCH
-- Gestione di eventuali altri errori (es. overflow)
DECLARE @ErrMsg NVARCHAR(4000) = ERROR_MESSAGE();
RAISERROR('Errore nel calcolo: %s', 16, 1, @ErrMsg);
SET @tempo = NULL;
END CATCH
END
GO
-- Test
DECLARE @risultato DECIMAL(10,2);
EXEC CalcolaTempoRaddoppio @r = 0.07, @tempo = @risultato OUTPUT;
SELECT @risultato AS AnniRaddoppio; -- circa 9.90 anni
Spiegazione
- LOG(2) in T‑SQL è il logaritmo naturale (base [math]e[/math]) di 2 (equivalente a LN(2) in altri RDBMS).
- Divisione per il tasso [math]r[/math]. Se [math]r[/math] è positivo, il risultato è finito.
- Arrotondamento a 2 decimali come richiesto.
- Gestione errori: TRY…CATCH cattura eccezioni (es. se [math]@r[/math] è troppo piccolo e il risultato overflow, anche se in pratica con decimali non succede). Il RAISERROR personalizzato rende il feedback chiaro.
💡 Osservazione
Una stored procedure ben progettata separa la logica di business (calcolo) dalla presentazione. L’uso di parametri OUTPUT e messaggi di errore strutturati la rende riutilizzabile in applicazioni esterne.
Esercizio 3.1 – Evoluzione del Codice: Gestione Errori Moderna con THROW (medio)
Perché questo aggiornamento?
Nell’Esercizio 3 abbiamo utilizzato RAISERROR per gestire i dati fuori dominio. Sebbene sia un costrutto storico e ancora molto diffuso, a partire da SQL Server 2012 Microsoft consiglia l’uso di THROW.
Il passaggio a THROW garantisce tre vantaggi architetturali fondamentali per chi progetta pipeline di dati:
- Interruzione immediata: A differenza di
RAISERROR(che con severità inferiori a 16 può permettere al codice di continuare l’esecuzione), THROW interrompe sempre e comunque il batch corrente, evitando che dati parzialmente elaborati o errati vengano scritti nelle tabelle di produzione. - Conservazione dello Stack Trace: Se usato senza parametri all’interno di un blocco CATCH,
THROW;rilancia l’errore originario esatto (incluso il numero di riga in cui si è verificato), rendendo il debug infinitamente più semplice. - Sintassi pulita e standard: Non richiede la formattazione complessa tipica di RAISERROR (come i parametri [math]\%s[/math] o i codici di stato manuali) e si allinea perfettamente con la gestione delle eccezioni di linguaggi come C# o Python (
raise/throw).
Soluzione
CREATE PROCEDURE CalcolaTempoRaddoppioModerno
@r DECIMAL(10,6),
@tempo DECIMAL(10,2) OUTPUT
AS
BEGIN
SET NOCOUNT ON;
BEGIN TRY
-- Validazione preventiva dell'input tramite THROW
IF @r <= 0 BEGIN -- THROW richiede: NumeroErrore (>= 50000), Messaggio, Stato (da 1 a 255)
THROW 50001, 'Il tasso di interesse deve essere strettamente positivo.', 1;
END
-- Calcolo: ln(2) / r
SET @tempo = ROUND(LOG(2) / @r, 2);
END TRY
BEGIN CATCH
-- In caso di errore (divisione per zero, overflow o errore personalizzato)
-- azzeriamo il parametro di output per sicurezza
SET @tempo = NULL;
-- Rilancia l'eccezione esatta che ha attivato il CATCH.
-- In questo modo, i sistemi di orchestrazione (ADF, Airflow)
-- cattureranno il fallimento originale con tutti i dettagli.
THROW;
END CATCH
END
GO
-- Test di funzionamento corretto
DECLARE @risultato DECIMAL(10,2);
EXEC CalcolaTempoRaddoppioModerno @r = 0.05, @tempo = @risultato OUTPUT;
SELECT @risultato AS AnniRaddoppio; -- Restituisce 13.86
-- Test per scatenare l'errore personalizzato
-- EXEC CalcolaTempoRaddoppioModerno @r = 0.0, @tempo = @risultato OUTPUT;
Analisi dei Miglioramenti Applicativi
Per capire la differenza pratica tra i due approcci, ecco un confronto diretto delle caratteristiche chiave:
| Caratteristica | RAISERROR (Legacy) | THROW (Moderno) |
|---|---|---|
| Severità dell’errore | Va specificata manualmente (es. 16, 1). Se si sbaglia il livello, l’errore potrebbe non bloccare l’esecuzione. | Fissata automaticamente a 16. Interrompe sempre il batch corrente. |
| Error Number | Richiede la registrazione preventiva in sys.messages per ID personalizzati, altrimenti usa di default l’ID 50000. | Permette di usare qualsiasi codice di errore personalizzato [math]\ge 50000[/math] direttamente “on-the-fly”. |
| Comportamento nel CATCH | Richiede di ricostruire manualmente il messaggio d’errore se si vuole propagare l’eccezione verso l’alto. | Con un semplice THROW; (senza parametri) rilancia l’errore originale intatto, mantenendo il numero di riga nativo. |
| Integrazione con ETL | Può inviare messaggi di avviso senza far fallire lo step di orchestrazione (comportamento rischioso in produzione). | Fa fallire in modo pulito e sicuro il job, allertando immediatamente i sistemi di monitoraggio. |
Domanda di riflessione
Quale proprietà dei logaritmi giustifica l’uso di LOG(2)/r per il tempo di raddoppio? E se il tasso fosse espresso in percentuale (es. 7 invece di 0.07), come modificheresti la procedura?
Risposte alle Domande di Riflessione
Esercizio 1
Domanda: Quale proprietà dei logaritmi hai utilizzato per definire il pH? E cosa succederebbe se la base del logaritmo fosse 2 invece di 10?
Risposta:
La definizione di pH si basa sulla proprietà del logaritmo decimale (base 10) per esprimere la concentrazione di ioni idrogeno su una scala logaritmica che comprime un ampio intervallo di valori (da [math]10^{-14}[/math] a [math]1[/math]) in un intervallo più gestibile (da 0 a 14).
Se usassimo base 2, il valore del pH cambierebbe perché il logaritmo in base 2 di una data concentrazione è diverso da quello in base 10. La relazione sarebbe [math]\text{pH}_2 = -\log_2([H^+])[/math]. Per convertire, si potrebbe usare il cambio di base: [math]\log_2(x) = \frac{\log_{10}(x)}{\log_{10}(2)}[/math]. In pratica, i valori numerici sarebbero diversi, ma la scala rimarrebbe logaritmica; la scelta della base è convenzionale e legata alla definizione originale del pH (Sørensen scelse la base 10 per comodità con le potenze di 10).
Esercizio 2
Domanda: Perché usiamo EXP invece di POWER con base fissa? Quale vantaggio offre la base [math]e[/math] in contesti di crescita continua?
Risposta:
La funzione EXP calcola [math]e^x[/math], dove [math]e[/math] è la costante di Nepero (≈2.71828). Nei modelli di crescita continua, l’equazione differenziale [math]\frac{dN}{dt} = rN[/math] ha come soluzione [math]N(t) = N_0 e^{rt}[/math]. La base [math]e[/math] è naturale perché è l’unica base per cui la derivata di [math]e^{rt}[/math] è [math]re^{rt}[/math], semplificando i calcoli analitici.
Utilizzare POWER con una base fissa (es. 2) richiederebbe di scrivere [math]2^{kt}[/math] e il tasso di crescita non sarebbe più [math]r[/math] ma un tasso equivalente, meno intuitivo. In pratica, EXP è più diretto per modelli continui e coincide con le funzioni matematiche standard nei linguaggi di programmazione.
Esercizio 3
Domanda: Quale proprietà dei logaritmi giustifica l’uso di LOG(2)/r per il tempo di raddoppio? E se il tasso fosse espresso in percentuale (es. 7 invece di 0.07), come modificheresti la procedura?
Risposta:
La formula del tempo di raddoppio deriva dall’equazione [math]Ce^{rt} = 2C[/math], dividendo per [math]C[/math] si ha [math]e^{rt} = 2[/math]. Prendendo il logaritmo naturale di entrambi i membri: [math]rt = \ln(2)[/math], quindi [math]t = \frac{\ln(2)}{r}[/math]. Questa è una diretta applicazione della proprietà che il logaritmo è l’inverso dell’esponenziale.
Se il tasso fosse espresso in percentuale (es. 7), dovremmo dividerlo per 100 per ottenere il tasso decimale [math]r = 0.07[/math]. Nella procedura, si potrebbe aggiungere un parametro booleano o accettare direttamente il valore in percentuale e convertirlo internamente: SET @r = @r / 100.0.
L’Impatto sulle Performance e la Diagnostica
Le funzioni matematiche (LOG, EXP, POWER, SQRT) sono operazioni scalari: il motore del database deve allocare cicli di CPU per valutarle singolarmente, riga per riga (row-by-row). Se su tabelle anagrafiche l’impatto è trascurabile, su dataset transazionali o log di sensori con decine di milioni di record il calcolo matematico a runtime diventa rapidamente il collo di bottiglia principale.
Misurare l’impatto: Execution Plan e Statistiche
Prima di ottimizzare, è fondamentale misurare. In T-SQL, l’equivalente del classico EXPLAIN ANALYZE si ottiene attivando SET STATISTICS TIME ON e analizzando l’Actual Execution Plan. Quando esegui una query con funzioni matematiche, cerca nel piano grafico l’operatore Compute Scalar. Se noti che questo nodo assorbe una percentuale sproporzionata del costo totale della query, o se i tempi di CPU (CPU time) superano ampiamente il tempo di esecuzione effettivo (Elapsed time), significa che il motore sta soffrendo sotto il peso del calcolo matematico. In questi casi, è il momento di intervenire.
Quando il risultato di un calcolo complesso viene interrogato frequentemente (ad esempio per alimentare dashboard o modelli predittivi downstream), conviene valutare strategie di materializzazione del dato:
- Colonne calcolate persistenti (Persisted Computed Columns): Aggiungendo PERSISTED alla definizione della colonna, il database esegue la funzione matematica solo durante l’operazione di INSERT o UPDATE, memorizzando il risultato su disco. Il costo si sposta dalla lettura (frequente) alla scrittura (meno frequente).
- Viste indicizzate (Indexed Views): Permettono di materializzare il risultato di join e calcoli complessi, servendo le query analitiche in tempi record (sfruttando gli indici clusterizzati sulla vista).
- Precomputazione durante l’ETL: Spostare l’onere del calcolo direttamente all’interno della pipeline di trasformazione, in modo che il data warehouse o il data mart espongano al frontend analitico una colonna numerica pura, già calcolata.
Attenzione alla SARGability:
Non applicare mai le funzioni matematiche direttamente nella clausola WHERE per filtrare i dati (es. WHERE LOG10(ConcentrazioneH) = 7.00). Inviluppare una colonna all’interno di una funzione “acceca” l’ottimizzatore, impedendo l’uso degli indici (Index Seek) e forzando il motore a eseguire una costosa lettura completa della tabella (Index Scan).
Perché questi esercizi sono fondamentali per un Data Engineer?
Quando si impara T-SQL, il 90% dei tutorial ci si ferma alle classiche query gestionali: calcolare il totale degli ordini, filtrare clienti per area geografica o unire tabelle di risorse umane.
Gli esercizi proposti qui sopra, invece, dimostrano il vero potenziale analitico del database.
Spostare calcoli scientifici, finanziari o predittivi complessi (come logaritmi, crescite esponenziali o modelli di decadimento) direttamente dentro SQL Server significa applicare il vero Data-centric computing.
Calcolare il tempo di raddoppio di un portafoglio finanziario tramite una Stored Procedure invece di esportare i dati in Python significa abbattere i tempi di latenza, ridurre i colli di bottiglia sulla rete e centralizzare la logica di business in un punto unico e sicuro.
La gestione preventiva delle eccezioni (come evitare logaritmi di numeri negativi) mostra inoltre come scrivere codice T-SQL production-ready, capace di girare di notte in un job automatizzato senza far crollare l’intera pipeline al primo dato anomalo.
📚 SQL avanzato, colloqui tecnici e Data Engineering moderno
Se vuoi consolidare le competenze richieste nei colloqui per Data Analyst, Data Engineer e SQL Developer, oppure approfondire le moderne architetture dati con Spark e Databricks, ecco una selezione di guide pratiche con esercizi, dataset e casi reali.
👉Differenza tra SQL e T-SQL: Guida Pratica per Data Analyst e Data Engineer
👉SQL per colloqui tecnici: 20 query essenziali con dataset, esercizi e soluzioni
👉Colloquio SQL per Data Analyst e Data Engineer – Parte 1
👉Colloquio SQL – Parte 2: GROUP BY, HAVING e Window Functions
👉Databricks: il laboratorio intelligente per dati, AI e automazione aziendale
👉Colloquio Databricks e PySpark: domande reali e risposte commentate
👉Guida pratica al Data Lakehouse: progetto completo con Python, Spark e Delta Lake





