Colloquio SQL per Data Analyst e Engineer: Domande, Esercizi e Spiegazioni Pratiche (Parte 1)

Cerca:

Generic selectors
Exact matches only
Search in title
Search in content
Post Type Selectors
domande colloquio tecnico sql data analyst

Siamo onesti: preparare un colloquio SQL studiando a memoria liste infinite di domande e risposte è il modo migliore per fare scena muta alla prima vera domanda tecnica.

Dopo aver visto 20 query fondamentali nel nostro articolo precedente, è il momento di fare un salto di qualità. Quando un Senior Data Engineer o un Lead Analyst ti fa una domanda apparentemente banale come “Che cos’è un NULL?” o “Che differenza c’è tra TRUNCATE e DELETE?”, non sta valutando la tua grammatica SQL. Sta cercando di capire se hai mai fatto crashare un database in produzione, se sai come vengono gestiti i dati sul disco e se hai chiaro il concetto di transazione.

In questa prima parte delle 100 domande che possono cambiarti un colloquio, non ti darò i “compitini” da ripetere a pappagallo.

L’obiettivo non è superare il test, ma dimostrare che sei un professionista a cui si possono affidare i dati aziendali senza sudare freddo.

Pubblicità

1. Fondamenti SQL: Oltre le definizioni

Le prime domande di un colloquio tecnico sembrano sempre innocue: cos’è SQL? Cos’è una tabella?
La verità? L’intervistatore non vuole la definizione da manuale. Vuole capire se sai come il motore del database ragiona dietro le quinte.

Cos’è SQL?

SQL (Structured Query Language) non è un linguaggio di programmazione procedurale dove spieghi come estrarre il dato riga per riga. È un linguaggio dichiarativo: gli descrivi cosa vuoi ottenere, e il Query Optimizer decide il piano di esecuzione più efficiente.
Al colloquio, dimostra di sapere che SQL non è solo SELECT. Si divide in macro-categorie fondamentali per l’amministrazione:

  • DDL (Data Definition Language): definisce la struttura (CREATE, ALTER, DROP).
  • DML (Data Manipulation Language): gestisce i dati (SELECT, INSERT, UPDATE, DELETE).
  • DCL (Data Control Language): gestisce i permessi (GRANT, REVOKE).
  • TCL (Transaction Control): gestisce la coerenza (COMMIT, ROLLBACK).
-- Esempio TCL: il pane quotidiano per non fare danni in produzione
BEGIN TRANSACTION;
UPDATE dipendenti SET nome = 'Alicia' WHERE id = 1;
COMMIT; -- Senza questo, i dati restano bloccati o non visibili agli altri

Il viaggio di una Query (e come ragiona l’Optimizer)

Hai scritto la tua query SELECT. Hai premuto Esegui. Cosa succede nel millisecondo successivo?

In un colloquio per posizioni Mid/Senior, pronunciare le parole “Cost-Based Optimizer” alza immediatamente il livello della conversazione.

Quando scrivi in SQL non stai dando ordini operativi, stai esprimendo un desiderio.

È il database che deve decidere come esaudirlo, seguendo un flusso rigoroso:

1. Parser

Analisi sintattica e semantica.
Il motore verifica prima di tutto la grammatica. Se hai dimenticato una virgola o scritto SELEC invece di SELECT, si ferma qui. Se la sintassi è corretta, passa all’analisi semantica interrogando il dizionario dei dati: le tabelle e le colonne richieste esistono davvero? L’utente ha i permessi per leggerle?

2. Query Optimizer

Il cervello strategico (Cost-Based).
Questa è la vera magia dei database relazionali. L’Optimizer prende la tua query e genera decine di possibili percorsi per estrarre i dati.

Deve decidere: conviene usare quell’indice? È meglio fare un Full Table Scan? In quale ordine devo eseguire le Join? Per prendere la decisione, usa le statistiche interne del database e stima un “costo” in termini di CPU, memoria e letture su disco (I/O) per ogni percorso, scegliendo quello più economico.

3. Execution Plan

Il passaggio da dichiarativo a procedurale.
Il percorso vincente scelto dall’Optimizer viene formalizzato nel Piano di Esecuzione (Execution Plan). È un vero e proprio albero di operazioni passo-passo. Il tuo codice SQL dichiarativo è stato appena trasformato in un algoritmo procedurale.

4. Storage Engine

Il braccio operativo.
Il piano di esecuzione viene consegnato al motore di archiviazione (es. InnoDB in MySQL). Lo Storage Engine esegue le istruzioni andando fisicamente a recuperare le pagine di dati dal disco o dalla memoria RAM cache (Buffer Pool), assemblando il risultato finale che vedi a schermo.

💡 La domanda da colloquio: “Come ottimizzi una query lenta?”

Un candidato Junior risponde: “Aggiungo degli indici a caso e spero che vada più veloce”.

Un candidato Senior risponde: “Metto EXPLAIN (o EXPLAIN ANALYZE) davanti alla query. Questo mi permette di leggere l’Execution Plan generato dall’Optimizer. Da lì capisco se il database sta ignorando i miei indici per colpa di statistiche non aggiornate o se sta facendo un Full Table Scan evitabile.”

Dimostrare di sapere che l’Optimizer è il “direttore d’orchestra” ti posiziona immediatamente a un livello di seniority superiore.

Cos’è una tabella e cos’è una chiave primaria?

Una tabella è l’incarnazione dell’algebra relazionale: una griglia bidimensionale dove ogni cella deve contenere un valore atomico. Ma la vera domanda trabocchetto verte quasi sempre sulla Primary Key (PK).

La chiave primaria garantisce unicità e non nullità. Ma la vera risposta che fa brillare gli occhi a un Senior è questa: in quasi tutti i motori relazionali (come InnoDB su MySQL o SQL Server), la PK crea automaticamente un indice cluster. Questo significa che i dati vengono scritti fisicamente sul disco ordinati secondo quella chiave, rendendo le ricerche per PK istantanee.

Forse potrebbe interessarti anche:  Importare CSV in SQLite con Python: Guida Pratica e Soluzioni agli 8 Errori Più Comuni

Il consiglio per il colloquio: Specifica sempre la differenza tra chiave naturale (es. Codice Fiscale, fragile perché può cambiare) e chiave surrogata (un intero auto-incrementante o un UUID, la scelta raccomandata nel 99% dei casi aziendali).

Qual è la differenza tra CHAR e VARCHAR?

Questa è la classica domanda che svela se hai davvero gestito volumi di dati o se hai solo fatto query su database giocattolo. La differenza sta nella gestione della memoria e nel padding.

Caratteristica CHAR(n) VARCHAR(n)
Lunghezza Fissa (n caratteri). Variabile (massimo n caratteri).
Occupazione Sempre n byte/caratteri. Riempie con spazi (padding) a destra fino a raggiungere n. Occupa la lunghezza effettiva della stringa + 1 o 2 byte di overhead.
Performance Teoricamente più veloce per dati a lunghezza fissa (il DB calcola gli offset fisici sul disco istantaneamente). Più efficiente per lo spazio, ma con un leggero costo CPU per estrarre la lunghezza.

Regola d’oro: Usa CHAR solo per campi a lunghezza fissa e corti (es. codici ISO come CHAR(2), sesso, flag). Usa VARCHAR per descrizioni o nomi.

CREATE TABLE test_stringhe (
    codice_stato CHAR(2) NOT NULL,      -- es. 'IT' (sempre 2)
    descrizione VARCHAR(255) NOT NULL   -- es. 'Italia' (6 + overhead)
);

INSERT INTO test_stringhe VALUES ('IT', 'Italia');

-- ATTENZIONE ALLA LUNGHEZZA AL COLLOQUIO
SELECT 
    codice_stato,
    CHAR_LENGTH(codice_stato) AS caratteri, -- Conta i caratteri
    LENGTH(codice_stato) AS byte_usati      -- Conta i byte fisici (in UTF-8 le accentate pesano di più!)
FROM test_stringhe;

💡 Perché questo esercizio è interessante dal punto di vista applicativo?
In contesti Big Data o di Data Warehousing con miliardi di righe, scegliere pigramente un VARCHAR(255) al posto di un CHAR(2) per un codice stato porta a un enorme spreco di risorse CPU durante l’I/O. Saper distinguere tra l’occupazione fisica (LENGTH) e logica (CHAR_LENGTH) dimostra che sai ottimizzare l’archiviazione e le query di lettura massive.

Che cosa rappresenta NULL?

NULL non è un valore (non è 0, non è ”).

È un marcatore che indica un dato sconosciuto o mancante.

Ha un comportamento “contagioso”: qualsiasi operazione matematica con un NULL restituisce NULL.

Le trappole classiche nei colloqui:

  • WHERE colonna = NULL non funziona mai. Due entità sconosciute non sono confrontabili. Si deve usare IS NULL.
  • Le funzioni aggregate (SUM, AVG, COUNT(colonna)) ignorano i NULL.
CREATE TABLE ordini (
    ordine_id INT PRIMARY KEY,
    data_spedizione DATE NULL         -- NULL = non spedito
);

INSERT INTO ordini VALUES (1, '2026-07-10'), (2, NULL);

-- ERRORE CLASSICO
SELECT * FROM ordini WHERE data_spedizione = NULL;  -- Restituisce 0 righe!  -- CORRETTO
SELECT * FROM ordini WHERE data_spedizione IS NULL; 

-- Funzioni aggregate
SELECT 
    COUNT(*) AS totale_righe,          -- 2
    COUNT(data_spedizione) AS spediti, -- 1 
    AVG(EXTRACT(DAY FROM data_spedizione)) -- Calcola solo sull'ordine 1!
FROM ordini;

-- Gestione corretta
SELECT COALESCE(data_spedizione, CURRENT_DATE) FROM ordini;

💡 Perché questo esercizio è un campo minato?
Questo test riflette i bug più comuni in produzione nei sistemi di reportistica aziendale. La query con AVG che ignora i NULL dimostra matematicamente come calcoli non attenti possano generare bias nei report (es. calcolare la media dei tempi di evasione escludendo inconsciamente gli ordini non spediti, falsando i KPI).

Qual è la differenza tra DELETE, TRUNCATE e DROP?

Questo è il discriminator definitivo tra un “utente di query” e un vero Data Engineer. Per rispondere bene, chiudiamo l’editor SQL e guardiamo come il motore gestisce lo spazio su disco.

Caratteristica DELETE TRUNCATE DROP
Tipo DML DDL (solitamente) DDL
Obiettivo Rimuove righe selettive (condizionale). Svuota tutta la tabella. Distrugge struttura e dati.
Logging Massivo (scrive ogni riga cancellata nel log). Minimo (registra solo la deallocazione). Minimo (rimuove i metadati).
Rollback Sempre possibile. Dipende dal DBMS (vedi giù). Dipende dal DBMS.

Cosa succede nel dettaglio?

DELETE (Rimozione riga per riga):

Legge ogni riga, scrive nel Transaction Log l’immagine “prima” e “dopo” per poter fare un eventuale rollback. I trigger vengono eseguiti.

L’impatto? Su milioni di righe, blocca il server, riempie il disco e crea frammentazione (lascia “buchi” fisici).

TRUNCATE (Rimozione a livello di pagina):

Non legge le righe. Dealloca interi extent (gruppi di pagine fisiche) dedicati alla tabella e resetta i contatori di auto-incremento. È come strappare un capitolo intero da un libro invece di cancellarlo lettera per lettera con la gomma. È fulmineo.

DROP (Rimozione strutturale):

Cancella i metadati dal dizionario del DB, dealloca le pagine fisiche ed elimina gli indici.

Vediamo lo script di simulazione che scriverei  durante il colloquio:

-- Preparazione ambiente
CREATE TABLE test_log (
    id INT AUTO_INCREMENT PRIMARY KEY,
    valore INT NOT NULL
);

-- Caso 1: DELETE in transazione 
START TRANSACTION;
    DELETE FROM test_log WHERE valore < 500;  -- Operazione loggata riga per riga
ROLLBACK;  -- Il log di undo riscrive tutto perfettamente.

-- Caso 2: TRUNCATE in transazione 
START TRANSACTION;
    TRUNCATE TABLE test_log;  
ROLLBACK;  
-- ATTENZIONE AL COLLOQUIO: 
-- In PostgreSQL: La tabella riappare con i dati. TRUNCATE è transazionale (MVCC-safe).
-- In MySQL/Oracle: Il TRUNCATE esegue un COMMIT IMPLICITO. Chiude la transazione corrente senza preavviso. Il Rollback fallisce e i dati sono persi per sempre!

💡 Perché questa simulazione è di livello Senior?
Discutere questo script porta il colloquio sull’architettura di sistema e le logiche ACID. Saper citare scenari di “disaster recovery” (come l’Undo/Redo dei Transaction Log) e conoscere la differenza fatale del commit implicito su MySQL rispetto a PostgreSQL certifica un’esperienza sul campo in ambito ETL reale, dove operazioni come TRUNCATE sono vitali per pulire le staging area in modo efficiente senza saturare i server.

Forse potrebbe interessarti anche:  Propensity Modeling: 6 Esercizi Pratici per Prevedere il Comportamento d'Acquisto

Passiamo dalla teoria alla pratica. Un buon intervistatore non si accontenta di farti parlare, vuole vederti scrivere codice e, soprattutto, vuole sentirti ragionare sui casi limite.

Ecco 6 esercizi progettati esattamente come i test tecnici per ruoli Mid/Senior in Data Analysis e Data Engineering.

Coprono le trappole più comuni su NULL, tipi di dato, cancellazioni e design relazionale.

Esercizio 1: L’inganno delle medie finanziarie

Scenario: Hai una tabella venditori con due colonne: nome_venditore e commissione_bonus. Se un venditore non ha ancora maturato un bonus, il sistema gestionale inserisce un NULL invece di 0.

nome_venditore commissione_bonus
Marco 1000
Giulia 1500
Luca NULL
Anna NULL

La richiesta: Scrivi una query per calcolare la commissione media di tutti i venditori del team.

La Soluzione

-- APPROCCIO SBAGLIATO (Cade il 50% dei candidati Junior)
SELECT AVG(commissione_bonus) AS media_errata FROM venditori; 
-- Risultato: 1250 (Calcola solo (1000+1500) / 2)

-- APPROCCIO CORRETTO
SELECT AVG(COALESCE(commissione_bonus, 0)) AS media_reale FROM venditori;
-- Risultato: 625 (Calcola (1000+1500+0+0) / 4)

💡 Perché questo esercizio è fondamentale?
Valuta la tua comprensione del rischio aziendale. La funzione AVG() ignora nativamente i NULL. Se presenti il primo dato (1250€) al CFO, stai gonfiando artificialmente le performance del team. Usare COALESCE dimostra che sai bonificare i dati mancanti prima che l’aggregazione matematica distorca i KPI.


Esercizio 2: Il buco nero del “NOT IN”

Scenario: Hai una tabella clienti (PK id_cliente) e una tabella ordini (con la colonna nullable id_cliente_fk).

Devi trovare tutti i clienti che non hanno mai effettuato un ordine.

La richiesta: Perché la seguente query potrebbe non restituire alcun risultato, anche se ci sono clienti senza ordini?

Come la riscriveresti in modo sicuro?

SELECT id_cliente, nome 
FROM clienti 
WHERE id_cliente NOT IN (SELECT id_cliente_fk FROM ordini);

La Soluzione

La query fallisce silenziosamente se la sottoclausola SELECT id_cliente_fk FROM ordini restituisce anche un solo valore NULL.

In SQL, la logica a tre valori impone che X NOT IN (1, 2, NULL) non sia valutato come VERO, ma come SCONOSCIUTO.

-- RISCRITTURA SICURA 1: Uso di NOT EXISTS (Ottima performance)
SELECT c.id_cliente, c.nome 
FROM clienti c
WHERE NOT EXISTS (
    SELECT 1 FROM ordini o WHERE o.id_cliente_fk = c.id_cliente
);

-- RISCRITTURA SICURA 2: Uso del LEFT JOIN (Anti-Join pattern)
SELECT c.id_cliente, c.nome 
FROM clienti c
LEFT JOIN ordini o ON c.id_cliente = o.id_cliente_fk
WHERE o.id_cliente_fk IS NULL;

💡 Perché questo esercizio è fondamentale?
Il bug del NOT IN con i NULL è una delle cause più subdole di anomalie nei report di Data Engineering. L’intervistatore vuole vedere se padroneggi i pattern NOT EXISTS o LEFT JOIN + IS NULL (noti come Anti-Join), che oltre a essere immuni al problema dei NULL, sono generalmente ottimizzati molto meglio dai motori relazionali rispetto al NOT IN.

Ottimizzazione e Costi: Perché EXISTS batte (quasi) sempre IN?

Un dettaglio tecnico che fa guadagnare immediata autorevolezza al colloquio riguarda il costo di esecuzione delle sottoquery.

Perché un professionista preferisce EXISTS (o NOT EXISTS) rispetto a IN?

Il segreto si chiama short-circuit evaluation. Quando utilizzi EXISTS, il motore del database si comporta in modo pigro ma intelligente: esplora la sottoquery e, nel momento esatto in cui trova la prima riga che soddisfa la condizione, interrompe la ricerca e passa al record successivo.

Al contrario, IN (e a maggior ragione NOT IN) costringe spesso l’Optimizer a leggere e materializzare l’intera sottoquery in memoria, per poi confrontare i valori. Su tabelle con milioni di righe, evitare la materializzazione in RAM permette di trasformare una query che impiega minuti in un’estrazione da pochi millisecondi.


Esercizio 3: La stringa fantasma (CHAR vs VARCHAR)

Scenario: Una tabella di staging utenti_tmp ha una colonna codice_nazione definita per errore come CHAR(4). A causa di un’estrazione grezza, il codice dell’Italia è stato importato come 'IT ' (IT seguito da due spazi).

La richiesta: Scrivi i comandi SQL per pulire fisicamente i dati dagli spazi in eccesso e per convertire la colonna nel tipo di dato corretto ed efficiente.

La Soluzione

-- 1. Pulizia dei dati (Rimuove gli spazi iniziali e finali)
UPDATE utenti_tmp 
SET codice_nazione = TRIM(codice_nazione);

-- 2. Ottimizzazione della struttura (DDL)
-- Essendo codici nazione standard (es. IT, FR, US), la lunghezza fissa è a 2.
ALTER TABLE utenti_tmp 
MODIFY codice_nazione CHAR(2) NOT NULL; 
-- (Nota: la sintassi esatta di ALTER TABLE varia leggermente tra MySQL, PostgreSQL e SQL Server)

💡 Perché questo esercizio è fondamentale?
Dimostra la transizione tra Data Analysis (manipolazione del dato) e Data Engineering (strutturazione del dato). Un Junior si fermerebbe all’uso di TRIM() in ogni sua query di lettura (sprecando cicli di CPU). Un Senior bonifica il dato alla radice tramite UPDATE e corregge lo schema per prevenire il problema in futuro, scegliendo saggiamente CHAR(2) invece di un generico VARCHAR.

Forse potrebbe interessarti anche:  Data Analyst Italia 2025: Guida Completa a Stipendi, Carriera e Opportunità

Esercizio 4: Pulizia del Data Warehouse

Scenario: Inizi il tuo turno e devi svuotare la tabella di transito log_eventi_staging che contiene 50 milioni di righe, per fare spazio al caricamento notturno. La tabella non ha chiavi esterne (Foreign Keys) che puntano a lei.

La richiesta: Quale comando esegui per svuotarla e perché?

Scrivi il comando.

La Soluzione

-- Comando corretto
TRUNCATE TABLE log_eventi_staging;

-- Comando da EVITARE in questo scenario:
-- DELETE FROM log_eventi_staging;

💡 Perché questo esercizio è fondamentale?
Se rispondi DELETE, non superi il colloquio per un ruolo ETL. DELETE su 50 milioni di righe registra ogni singola cancellazione nel log delle transazioni. Risultato: impiegherà ore, bloccherà la tabella e saturerà il disco del server. TRUNCATE dealloca semplicemente le pagine fisiche di memoria: finisce in millisecondi e azzera anche gli indici di autoincremento, lasciando la tabella perfettamente intonsa per il nuovo caricamento.


Esercizio 5: Deduplicazione brutale

Scenario: A causa dell’assenza di un vincolo UNIQUE, nella tabella newsletter (colonne: id autoincrementante PK, email) ci sono email duplicate.

La richiesta: Scrivi una singola istruzione DELETE che elimini i duplicati, mantenendo nel database solo il record con l’id più basso per ogni email.

La Soluzione

-- Soluzione con Self-Join (Molto efficiente, sintassi tipica MySQL)
DELETE t1 
FROM newsletter t1
JOIN newsletter t2 
  ON t1.email = t2.email 
  AND t1.id > t2.id;

-- Alternativa standard ANSI (Usando una CTE o Subquery)
DELETE FROM newsletter 
WHERE id NOT IN (
    SELECT MIN(id) 
    FROM newsletter 
    GROUP BY email
);

💡 Perché questo esercizio è fondamentale?
Nessun database nel mondo reale è pulito. L’assenza di constraint porta inevitabilmente a record duplicati. Questa query verifica la capacità di incrociare una tabella con se stessa (Self-Join) e applicare logiche di disuguaglianza (t1.id > t2.id) per identificare chirurgicamente quali tuple storiche sacrificare.


Esercizio 6: System Design della Chiave Primaria

Scenario: Devi creare da zero una tabella clickstream che registrerà ogni click degli utenti sul sito.

Prevedi circa 500 INSERT al secondo.

La richiesta: L’architetto del software ti propone di usare la funzione UUID() (es. 550e8400-e29b-41d4-a716-446655440000) come Chiave Primaria (Primary Key) per evitare collisioni.

Accetti? Giustifica la tua risposta e scrivi il DDL che riterresti ottimale.

La Soluzione

-- DDL Proposto dal candidato
CREATE TABLE clickstream (
    id BIGINT AUTO_INCREMENT PRIMARY KEY, -- Rifiuto l'UUID
    user_id INT NOT NULL,
    timestamp_click TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
    url_visitata VARCHAR(500)
);

💡 Perché questo esercizio è fondamentale?
Questa è la “Boss Fight” del colloquio. Accettare un UUID come Primary Key in una tabella ad altissima frequenza di inserimento è un errore architetturale grave nei database basati su indici cluster (come InnoDB). Gli UUID sono stringhe lunghe (36 caratteri) e, soprattutto, casuali.

Inserire dati casuali costringe il disco a riordinare continuamente i nodi fisici dell’albero (B-Tree), causando un fenomeno disastroso chiamato Page Split (frammentazione del disco). Utilizzando un BIGINT AUTO_INCREMENT, gli inserimenti sono sequenziali: il database scrive i nuovi record alla fine del file ad altissima velocità, massimizzando le performance di I/O.

Il percorso continua

Questo articolo è solo la punta dell’iceberg. Nel prossimo articolo spiegheremo la differenza tra una GROUP BY e una Window Function con esercizi reali.

Gli strumenti di AI oggi generano query SQL in pochi secondi, ma è il professionista umano che deve comprendere il modello fisico dei dati, anticipare le criticità di sistema e scegliere l’approccio che non faccia cadere il database di produzione.

Ecco perché l’SQL, inteso come architettura e non solo come sintassi, rimarrà centrale per molto tempo.


Colloqui tecnici,  Data Science, Data Engineering e AI

👉Checklist tecnico per Data Scientist: la guida completa al colloquio entry level

👉Colloquio Databricks e PySpark: domande reali e risposte per superarlo

👉Perché i conti non quadrano: cinque casi reali per brillare ai colloqui

👉Quadratura dei dati e Mass Balance: controlli di integrità con SQL e Python

👉Come diventare Junior AI Engineer: cosa chiedono davvero le aziende

👉Leggere tra le righe di un annuncio da Data Engineer

Pubblicità