
SQL, notebooks, and data storytelling with AI
SQL, notebooks and data storytelling with AI on GinnyTech: establishing when AI can propose code and when analytical code review with controls, ownership and reviewable outputs is needed.
What you will learn
- Design AI workflows for data with controls, owners, and reviewable outputs
- Apply AI, AutoML or agentic AI to business analytics cases without losing rigor
- Recognize risks of leakage, drift, cost, privacy, and ungoverned automation
SQL, notebooks, and data storytelling with AI
Siamo nel binario dei sistemi LLM, e l’esperienza che voglio raccontarti è probabilmente già tua: chiedi a un modello la query per la retention mensile e in dieci secondi hai una CTE elegante, nomi sensati, commenti pure convincenti. Il problema è che gira, restituisce numeri, e quei numeri sono sbagliati: conta i riattivati come nuovi utenti, gonfia il denominatore, e nessuno se ne accorge finché il grafico non finisce in riunione. Questa è l’esperienza quotidiana di chi usa l’AI sui dati: velocità straordinaria in scrittura, fragilità silenziosa nella semantica. La differenza tra un analista che subisce l’output e uno che lo governa sta tutta qui, nel ciclo che lega domanda, contesto, controllo e narrazione.
Il confine che questa lezione fissa
Questa lezione stabilisce quando l’AI può proporre query e narrazioni e quando serve una revisione analitica con grain verificato, totali quadrati e un responsabile che firma il numero. In una frase: il modello scrive, ma solo il revisore umano può dire che il numero è vero.
La procedura in sei mosse
Il percorso che rende governabile un output generato si compone di questi passaggi.
- Dichiara per iscritto tabelle, grain, definizioni di metrica con numeratore e denominatore, filtri obbligatori e vincoli di privacy.
- Fai generare la query con il commento delle assunzioni e una query di controllo allegata.
- Esegui il test di grain e la quadratura dei totali contro una fonte indipendente prima di leggere il risultato.
- Perturba finestre e soglie arbitrarie e scarta le letture che cambiano segno.
- Separa nel memo osservato, calcolato e ipotizzato, con limiti dichiarati e revisione ostile superata.
- Pubblica solo con proprietario nominato, versione dei dati e tracciabilità di prompt, modello e query.
Perché il SQL generato dall’AI sembra giusto anche quando sbaglia
I modelli linguistici sono ottimizzati per produrre testo plausibile, non query corrette. Questa distinzione è tutto. Un JOIN scritto bene a livello sintattico può nascondere tre errori semantici diversi: grain sbagliato, filtro mancante, finestra temporale incoerente. E poiché l’output ha l’aspetto del codice scritto da un senior — CTE nominate bene, indentazione pulita, alias coerenti — il revisore abbassa la guardia. È il fenomeno che alcuni chiamano fluency bias: scambiamo la fluidità dell’esposizione per accuratezza del contenuto.
Gli errori tipici del SQL generato ricadono in poche famiglie ricorrenti. La prima è la duplicazione da join fan-out: il modello unisce orders with order_items e poi calcola COUNT(DISTINCT customer_id) o peggio SUM(order_total), duplicando il totale per ogni riga dettaglio. La seconda è il filtro temporale applicato al posto sbagliato: la condizione sugli eventi finisce nel WHERE esterno invece che nella condizione di join, trasformando un LEFT JOIN in un INNER JOIN di fatto ed escludendo silenziosamente gli utenti senza eventi. La terza è la finestra di osservazione incompleta: calcolare la retention a 30 giorni su una coorte attivata 12 giorni fa, senza imporre che ogni utente abbia avuto davvero 30 giorni di osservabilità. Il numero esce, sembra preciso al secondo decimale, e non significa nulla.
C’è poi una classe di errori più sottile, quella delle assunzioni sullo schema. Il modello non vede il tuo warehouse: indovina. Presuppone che user_id sia univoco in users (magari c’è storicizzazione e non lo è), che amount sia in euro (magari è in centesimi), che status = 'active' significhi quello che pensi (magari include i trial). Ogni assunzione non dichiarata è un debito semantico che paghi quando il numero viene contestato. La regola operativa è semplice: mai eseguire SQL generato senza aver prima letto lo schema reale, e mai fidarsi di una query che non dichiara le proprie assunzioni in un commento iniziale.
Dare contesto al modello: schema, grain e definizioni di metrica
Il prompt “scrivimi la query per la retention” è un prompt pigro e produce query pigre. Un prompt professionale per SQL assomiglia più a una specifica di requirements: dichiara le tabelle con le colonne rilevanti e i tipi, il grain di ogni tabella (una riga = cosa?), le chiavi e la loro unicità reale, i filtri canonici da applicare sempre, e la definizione esatta della metrica con numeratore e denominatore. Solo con questo materiale il modello smette di indovinare e inizia a comporre.
Il grain merita un’attenzione particolare perché è la causa prima della maggior parte degli errori. Prima di scrivere qualsiasi aggregazione devi saper rispondere: una riga di questa tabella rappresenta cosa? Un ordine, una riga d’ordine, un evento, uno snapshot giornaliero per utente? E dopo il join, il grain cosa diventa? Se unisci una tabella a grain ordine con una a grain riga d’ordine, il risultato è a grain riga d’ordine. Ogni metrica a livello ordine va quindi pre-aggregata prima del join, non dopo. Questa è la regola d’oro che l’AI viola più spesso: aggrega al grain giusto prima di unire, mai dopo. Un prompt efficace lo dice esplicitamente: “la tabella orders ha una riga per ordine, order_items ha una riga per riga d’ordine; pre-aggrega a livello ordine prima di unire”.
Le definizioni di metrica vanno trattate come contratti, non come intuizioni. “Conversion rate” non significa nulla finché non specifichi: numeratore (utenti con almeno un acquisto entro 7 giorni dalla prima visita?), denominatore (tutti i visitatori o solo quelli tracciati con consenso?), finestra di attribuzione, trattamento dei rimbalzi, segmentazione. Le organizzazioni mature tengono un dizionario metriche — dbt semantic models, LookML, semplici file YAML versionati — e lo incollano nel prompt o lo espongono via retrieval. Senza, ogni query generata ridefinisce la metrica da zero, e due analisi dello stesso KPI divergono senza che nessuno capisca perché.
Un template pratico di prompt per SQL analitico contiene sei blocchi: obiettivo decisionale in una frase, DDL o descrizione schema delle tabelle coinvolte, grain dichiarato per tabella, definizione della metrica con numeratore e denominatore, vincoli (finestre temporali, filtri obbligatori, policy privacy come “niente email in chiaro”), e formato di output richiesto (CTE nominate, commento con assunzioni, sanity check finale). Quest’ultimo punto è sottovalutato: chiedere al modello di chiudere ogni query con una query di controllo — conteggio righe per grain, confronto somma delle parti contro totale — sposta una parte del controllo dentro la generazione stessa.
Pattern SQL che reggono alla revisione
Alcune strutture ricorrono in quasi ogni analisi e vale la pena conoscerle a memoria, così da riconoscere quando l’output del modello devia dal pattern sano. Il primo è l’analisi per coorti, la spina dorsale di retention e LTV. La forma corretta isola prima la coorte con la data di attivazione, poi impone la maturità dell’osservazione — solo coorti abbastanza vecchie da aver completato la finestra — e solo dopo misura il comportamento:
-- Coorte di attivazione con finestra di osservazione completa.
-- Assunzioni: una riga per utente in users; events a grain evento.
WITH cohort AS (
-- Data di prima attivazione per utente
SELECT
user_id,
MIN(event_date) AS activation_date
FROM events
WHERE event_type = 'activation'
GROUP BY user_id
),
mature AS (
-- Solo coorti con 30 giorni di osservabilita completa
SELECT user_id, activation_date
FROM cohort
WHERE activation_date <= CURRENT_DATE - INTERVAL '30 days'
),
retained AS (
-- Utenti con almeno un evento nei 30 giorni successivi
SELECT DISTINCT m.user_id
FROM mature m
JOIN events e
ON e.user_id = m.user_id
AND e.event_date > m.activation_date
AND e.event_date <= m.activation_date + INTERVAL '30 days'
)
SELECT
DATE_TRUNC('month', m.activation_date) AS mese_coorte,
COUNT(*) AS utenti_coorte,
-- Numeratore e denominatore sullo stesso insieme maturo
COUNT(r.user_id) AS ritenuti_30gg,
ROUND(COUNT(r.user_id) * 1.0 / COUNT(*), 4) AS retention_30gg
FROM mature m
LEFT JOIN retained r USING (user_id)
GROUP BY 1
ORDER BY 1;
Il secondo pattern è il funnel con step ordinati, dove l’errore classico del modello è contare gli utenti per step indipendentemente — gonfiando i tassi perché chi salta uno step viene contato comunque. La versione robusta ancora ogni step al precedente con join condizionali sulle timestamp, così la conversione da step 2 a step 3 è calcolata solo su chi ha completato lo step 2. Il terzo pattern è l’attribuzione con finestra: ogni evento di conversione va attribuito al tocco precedente entro N giorni, con regola di spareggio dichiarata (ROW_NUMBER() ordinato per distanza temporale) invece del FIRST_VALUE implicito che il modello tende a usare senza PARTITION BY corretto.
Notebook assistiti: velocità senza perdere riproducibilità
Il notebook è il luogo dove l’AI dà il meglio e fa i danni peggiori. Da un lato, generare boilerplate pandas, plot esplorativi e test statistici in secondi è un acceleratore reale: un profilo dati che a mano richiede mezz’ora esce in un prompt. Dall’altro, il notebook assistito tende all’entropia: celle eseguite fuori ordine, variabili sovrascritte da snippet incollati, dipendenze implicite dallo stato in memoria. Il risultato classico è il notebook che “funziona sulla mia macchina adesso” e non si riesegue da zero tra una settimana. La disciplina che risolve il problema è vecchia quanto i notebook stessi — riesecuzione top-to-bottom pulita — ma con l’AI va resa esplicita, perché la tentazione di incollare ed eseguire al volo è continua.
Tre abitudini separano un notebook assistito professionale da un collage di snippet. Primo, ogni cella generata viene letta prima di essere eseguita, con attenzione alle operazioni distruttive: dropna, fillna, cast di tipi, filtri che restringono il dataframe senza log. L’AI applica queste trasformazioni con disinvoltura — un df.dropna() buttato lì elimina una fetta di righe senza avvisare — e il costo si vede solo a valle, nei totali che non quadrano. Secondo, le assunzioni sui dati diventano assertion eseguibili subito dopo il caricamento: unicità delle chiavi, intervalli ammessi, completezza temporale. Terzo, il notebook viene blindato con seed fissati, versioni delle librerie registrate in testa, e parametri (date di inizio e fine, soglie, segmenti) raccolti in una sola cella di configurazione invece di sparsi come letterali magici.
# Cella di configurazione: tutti i parametri in un solo punto
DATA_INIZIO = "2026-01-01"
DATA_FINE = "2026-06-30"
SOGLIA_CONVERSIONE = 0.05 # soglia minima di interesse business
SEED = 42
# Controlli di integrita subito dopo il caricamento
# Verificano le assunzioni prima che l'analisi prosegua
assert df["user_id"].notna().all(), "user_id nulli trovati"
assert (df["amount_cents"] >= 0).all(), "importi negativi sospetti"
assert df["event_date"].between(DATA_INIZIO, DATA_FINE).all(), \
"date fuori finestra di analisi"
Sul fronte statistico, l’AI è bravissima a proporre test e pessima a verificarne le precondizioni. Ti scrive un t-test in un lampo ma non controlla normalità, eteroschedasticità o indipendenza delle osservazioni. Ti calcola un lift a due decimali senza intervallo di confidenza né potenza. La regola è chiedere sempre l’intervallo di confidenza della differenza tra i tassi: un lift (differenza relativa tra i tassi) senza incertezza quantificata è un aneddoto con due decimali. E quando i gruppi sono sbilanciati nel tempo — tipico dei rollout graduali — diffida dei confronti grezzi e stratifica per settimana prima di aggregare, altrimenti il paradosso di Simpson ti regala conclusioni invertite.
Controlli che distinguono un’analisi seria da una demo
Ogni output generato con l’AI dovrebbe passare una batteria di controlli standard prima di circolare. Il primo è il test di grain: la query restituisce il numero di righe atteso per il grain dichiarato? Se raggruppi per mese e utente e ottieni più righe delle combinazioni possibili, hai un fan-out nascosto. Il controllo si scrive in una riga — confronto tra COUNT(*) e COUNT(DISTINCT chiave) — e intercetta la famiglia di errori più frequente. Il secondo è la quadratura dei totali: la somma dei segmenti deve eguagliare il totale non segmentato entro una tolleranza dichiarata. Quando il modello applica filtri diversi nei due rami della query, la quadratura salta, e quel salto è il sintomo più economico da verificare.
Il terzo controllo è la baseline di confronto: ogni metrica va letta contro un riferimento — periodo precedente, gruppo di controllo, valore atteso da modello storico. Un “+8% di conversione” senza baseline è rumore presentabile. Il quarto è il controllo di leakage temporale, critico quando l’AI aiuta a costruire feature per modelli: qualsiasi colonna che incorpora informazione disponibile solo dopo l’evento target gonfia le performance in validazione e crolla in produzione. La domanda da porre a ogni feature è brutale e semplice: “questo dato esisteva davvero nel momento in cui il modello dovrà decidere?” Se la risposta richiede più di una frase, la feature è sospetta.
| Control | Cosa intercetta | Costo di esecuzione |
|---|---|---|
| Test di grain | Join fan-out, chiavi duplicate | Una query di conteggio |
| Quadratura totali | Filtri incoerenti tra rami | Confronto segmentato contro totale |
| Baseline storica | Rumore spacciato per segnale | Confronto con periodo precedente |
| Leakage temporale | Feature che guardano al futuro | Audit punto temporale per colonna |
| Stabilità al filtro | Risultati fragili a una soglia | Riesecuzione con soglie perturbate |
L’ultimo controllo della tabella merita enfasi perché è quello che l’AI non ti propone mai da sola: perturba le soglie arbitrarie — la finestra di 7 giorni diventa 5 e 10, la soglia di attività si alza e si abbassa — e osserva se la direzione della conclusione sopravvive. Se invertire una soglia ragionevole inverte il verdetto, non hai un insight: hai un artefatto parametrico. Le analisi che sopravvivono alla perturbazione sono quelle che puoi difendere in riunione.
Dal numero alla narrazione: data story che reggono alle obiezioni
Un numero corretto non convince nessuno da solo. La differenza tra un report che viene letto e uno che sposta una decisione sta nella struttura narrativa — e qui l’AI è un ottimo assistente di scrittura ma un pessimo garante della sostanza. Usala per generare varianti di formulazione, per stress-testare la tua argomentazione (“quali obiezioni solleverebbe un CFO scettico?”), per riassumere analisi lunghe in abstract mirati per audience diverse. Non usarla per decidere cosa significano i dati: quella è la parte che richiede giudizio, conoscenza del dominio e responsabilità personale.
Una data story solida segue un arco prevedibile: situazione, complicazione, svolta analitica, implicazione. La situazione fissa il contesto decisionale in due frasi — quale scelta va presa, con quale vincolo di tempo o budget. La complicazione introduce la tensione nei dati: la conversione è scesa nel segmento mobile mentre il desktop tiene, e il calo coincide con un cambio di layout. La svolta analitica mostra il lavoro che separa correlazione e spiegazione: hai stratificato per sorgente di traffico, escluso l’effetto mix, verificato che il calo persiste a parità di composizione. L’implicazione chiude con raccomandazione, incertezza residua e prossimo controllo: rollback del layout su una quota di traffico, monitoraggio per due settimane, soglia di decisione dichiarata in anticipo.
Il punto dove la maggior parte delle narrazioni crolla è il passaggio dalla correlazione alla causa. L’AI genera frasi causali con facilità allarmante — “il calo è dovuto al nuovo layout” — quando i dati mostrano solo concomitanza. La disciplina consiste nel graduare il linguaggio probatorio: i dati mostrano, suggeriscono, sono compatibili con, escludono. “Compatibili con” è la formulazione più onesta e più usata dagli analisti esperti: i numeri sono compatibili con l’ipotesi layout ma non escludono l’effetto stagionalità, e il test proposto discriminerà tra le due. Insegnare al modello questa gradazione — con istruzioni di stile esplicite nel prompt — migliora visibilmente la qualità delle bozze narrative.
C’è un test semplice per valutare se una story è pronta: la prova del revisore ostile. Consegna il memo a un collega con l’istruzione di smontarlo, o chiedi al modello stesso di farlo assumendo il ruolo. Le obiezioni serie ricadono quasi sempre in cinque categorie: denominatore sbagliato, periodo scelto ad hoc, segmento ignorato che inverte il risultato, confondente non controllato, incertezza non quantificata. Se la tua narrazione risponde in anticipo ad almeno quattro su cinque — con una sezione limiti dichiarati in calce — reggerà anche alla riunione vera. Il memo che non contiene la parola “limite” è un memo non finito.
Visualizzazioni oneste: scegliere il grafico che non mente
L’AI genera codice per grafici in secondi, ma la scelta del grafico resta una decisione retorica che richiede intenzione. Ogni tipo di visualizzazione enfatizza un messaggio diverso dagli stessi dati: la linea enfatizza il trend, la barra il confronto, lo scatterplot la relazione, l’istogramma la distribuzione. Il modello sceglie di default il grafico più comune per quel tipo di dati, non quello più adatto al tuo argomento. Dire al modello quale messaggio deve emergere — “voglio mostrare che il calo è concentrato in un segmento” — produce scelte migliori che chiedergli semplicemente “fammi un grafico”.
Gli inganni visivi più frequenti nei grafici generati sono noti e prevenibili. L’asse Y troncato che trasforma un calo del 2% in un precipizio: si previene imponendo lo zero o dichiarando il troncamento nel titolo. La doppia scala con assi manipolati per far coincidere due serie non correlate: meglio due pannelli separati o la normalizzazione a indice 100. L’aggregazione che nasconde la varianza — la media mensile piatta che occulta oscillazioni giornaliere enormi: affianca sempre una misura di dispersione, bande interquartili o almeno i punti grezzi in trasparenza. E il cherry-picking del periodo, il più difficile da intercettare: il grafico parte esattamente dal picco per mostrare un declino, o si ferma prima del rimbalzo.
# Grafico onesto: asse a zero, incertezza visibile, periodo dichiarato
# I punti grezzi impediscono alla media di nascondere la varianza
import matplotlib.pyplot as plt
fig, ax = plt.subplots(figsize=(10, 5))
ax.plot(date, media_mobile, label="Media mobile 7gg")
ax.fill_between(date, q25, q75, alpha=0.25, label="Banda interquartile")
ax.scatter(date_grezze, valori_grezzi, alpha=0.15, s=8)
ax.set_ylim(bottom=0) # asse a zero: niente precipizi artificiali
ax.set_title("Conversione mobile, gen-giu 2026 (finestra completa)")
ax.legend()
Una convenzione che ripaga sempre: ogni grafico porta nel titolo o nel sottotitolo il periodo coperto, la popolazione misurata e la dimensione del campione. Quando chiedi al modello il codice del grafico, includi questi elementi nei requisiti: è più facile ottenerli in generazione che aggiungerli dopo.
Governance pratica: costi, privacy e ownership del workflow
Sulla proprietà, la regola è senza eccezioni: ogni artefatto generato con l’AI ha un owner umano nominato che lo ha letto, verificato e firmato. “L’AI ha scritto la query” non è una difesa quando il numero è sbagliato, così come “il foglio l’ha fatto il gestionale” non lo è mai stato. Nei team che funzionano, la review delle query critiche è checklistata — grain verificato, quadratura passata, baseline allegata, limiti dichiarati — e la checklist è firmata. Il modello propone, l’umano dispone e risponde.
La privacy va progettata prima del primo prompt, non rattoppata dopo. Mai incollare dati personali o identificativi in un assistente esterno senza garanzie contrattuali chiare: il campione realistico va anonimizzato o sintetizzato prima di uscire dal perimetro. Le tecniche pratiche sono consolidate — mascheramento degli identificatori diretti, generalizzazione delle quasi-chiavi come CAP e data di nascita, soglie di cardinalità minima (HAVING COUNT(*) >= 10) per impedire l’identificazione in segmenti piccoli. Per lo sviluppo e i test, un dataset sintetico che preserva distribuzioni e vincoli referenziali basta nel 90% dei casi, e ha il vantaggio di poter circolare liberamente nei prompt. La domanda guida è: il modello ha davvero bisogno di vedere i dati reali, o gli bastano schema più statistiche?
I costi seguono una dinamica che sorprende chi viene dal mondo BI tradizionale. La query SQL costa compute del warehouse; la generazione AI costa token; l’agente autonomo che itera dieci volte su una query difficile costa entrambi, moltiplicati. Su volumi enterprise, un workflow agentico che profila tabelle enormi a ogni iterazione brucia budget in silenzio. Le contromisure sono operative: campionamento per le esplorazioni (TABLESAMPLE o tabelle aggregate), limiti di iterazioni per gli agenti, cache dei risultati intermedi, scelta del modello commisurata al compito — non serve il modello più grande per formattare una GROUP BY. Misura fin da subito due metriche di efficienza: costo per insight validato ed errori intercettati per ora di review. Se il primo sale e il secondo scende, stai automatizzando la produzione di rumore.
Il filo che lega queste tre dimensioni è la tracciabilità: per ogni numero che circola devi poter ricostruire prompt, versione del modello, snapshot dei dati, query eseguita e revisore. Un risultato che non puoi ricostruire non è un risultato: è un’opinione formattata bene. L’AI ha spostato il collo di bottiglia dell’analisi dalla scrittura alla verifica, e il professionista assistito non è quello che genera di più: è quello che scarta meglio.
Verdetto: l’AI propone query, grafici e bozze narrative, ma grain, quadrature e linguaggio probatorio restano umani; senza firma e tracciabilità il numero non circola.
Il caso di Google Flu Trends
Google Flu Trends, lanciato nel 2008, prometteva di prevedere l’influenza dalle ricerche online. Nella stagione 2012-2013 sovrastimò i casi di oltre il doppio rispetto ai dati dei centri americani per il controllo delle malattie. L’errore, ricostruito sulla rivista Science nel 2014, nacque da segnali mai ricalibrati e da un modello che nessuno aveva sottoposto a controlli indipendenti. È il caso da manuale di output plausibile mai verificato: trend perfetto nel grafico, numeri sbagliati nella realtà.
Domande per chiudere la lezione
- Quale controllo rivela una join che duplica le righe prima che il numero finisca in un report?
- Cosa deve contenere il prompt di una query perché il revisore possa verificarla senza indovinare?
- Quando una finestra di osservazione incompleta invalida una retention a 30 giorni?
- Quali tre elementi rendono un grafico onesto e difendibile in riunione?
Bloccato su questo argomento o vuoi applicarlo al tuo caso? Prenota una call di 15 minuti con un analista esperto.
Related Path
Lessons to read together
Questi collegamenti portano la lezione dentro il resto del corso: basi da riprendere, passaggi successivi e connessioni tematiche tra moduli.