Domande da colloquio SQL, con risposte che puoi eseguire

La maggior parte dei colloqui SQL ripete la stessa dozzina di idee. Qui sotto trovi quelle più comuni, con una risposta breve e una query che funziona in PostgreSQL. Gli esempi usano un piccolo database di una panetteria (customers, products, orders e order_items), così puoi provarne ciascuno in una lezione di questo sito.

Trova il secondo valore più alto

Una risposta semplice è ordinare in modo decrescente e saltare una riga. Restituisce una sola riga anche se più righe condividono il secondo prezzo.

Se i pari merito contano, classifica con DENSE_RANK e tieni il rango 2. DENSE_RANK non lascia buchi dopo i pari merito, quindi il rango 2 è sempre il secondo valore distinto.

SELECT name, unit_price
FROM (
  SELECT name, unit_price, DENSE_RANK() OVER (ORDER BY unit_price DESC) AS r
  FROM products
) AS ranked
WHERE r = 2;

Risultato: La fetta di torta al cioccolato, a 4.20.

Trova i duplicati

Raggruppa per la colonna che dovrebbe essere univoca e tieni i gruppi con più di una riga.

SELECT order_date, COUNT(*) AS orders
FROM orders
GROUP BY order_date
HAVING COUNT(*) > 1
ORDER BY order_date;

Risultato: Tre date, ciascuna con due ordini: 1, 2 e 6 settembre.

Trova le righe senza corrispondenza

Usa un LEFT JOIN e tieni le righe in cui il lato destro è NULL, oppure usa NOT EXISTS. Entrambe rispondono a «quali clienti non hanno mai ordinato?».

Evita NOT IN con una sottoquery che può restituire NULL: un solo NULL rende tutta la condizione sconosciuta e non ottieni nessuna riga.

SELECT c.first_name, c.last_name
FROM customers AS c
LEFT JOIN orders AS o ON o.customer_id = c.customer_id
WHERE o.order_id IS NULL
ORDER BY c.customer_id;

Risultato: Grace Kim e Hugo Meyer.

Prima riga per gruppo

Classifica all'interno di ogni gruppo con PARTITION BY, poi tieni il rango 1 in una query esterna. Usa RANK per mantenere i pari merito, oppure ROW_NUMBER per avere esattamente una riga per gruppo.

SELECT category, name, units
FROM (
  SELECT p.category, p.name, SUM(oi.quantity) AS units,
         RANK() OVER (PARTITION BY p.category ORDER BY SUM(oi.quantity) DESC) AS rk
  FROM products AS p
  JOIN order_items AS oi ON oi.product_id = p.product_id
  GROUP BY p.category, p.name
) AS ranked
WHERE rk = 1
ORDER BY category, name;

Risultato: Il prodotto più venduto di ogni categoria. Cake mostra due prodotti perché pareggiano a 3 unità.

Totale progressivo

Usa SUM come funzione finestra con ORDER BY. Ordina per una colonna senza pari merito, altrimenti le righe con lo stesso valore vengono sommate insieme.

SELECT order_id,
       SUM(quantity) AS units,
       SUM(SUM(quantity)) OVER (ORDER BY order_id) AS running_units
FROM order_items
GROUP BY order_id
ORDER BY order_id;

Risultato: Unità per ordine e totale progressivo, che termina a 56.

WHERE contro HAVING

WHERE filtra le righe prima del raggruppamento, quindi non può usare una funzione di aggregazione. HAVING filtra i gruppi dopo l'aggregazione. Nell'esempio, WHERE elimina prima i prodotti economici, e HAVING mantiene le categorie con almeno due dei prodotti rimasti.

SELECT category, COUNT(*) AS products
FROM products
WHERE unit_price > 2.50
GROUP BY category
HAVING COUNT(*) >= 2
ORDER BY category;

Risultato: Pane e torta, con due prodotti ciascuno sopra 2.50. Pasticceria e bevande hanno un solo prodotto sopra 2.50 e vengono escluse.

UNION contro UNION ALL

UNION rimuove le righe duplicate, e questo richiede lavoro extra. UNION ALL mantiene tutto ed è più veloce. Usa UNION ALL a meno che tu non debba rimuovere i duplicati.

SELECT city FROM customers
UNION
SELECT city FROM customers
ORDER BY city;

Risultato: Cinque città, ciascuna una volta. Con UNION ALL ce ne sarebbero 16.

Domande comuni

Qual è la differenza tra DELETE, TRUNCATE e DROP?
DELETE rimuove le righe che scegli (WHERE) e può essere annullato. TRUNCATE svuota rapidamente l'intera tabella e ne mantiene la struttura. DROP elimina la tabella stessa, con la sua struttura e i suoi dati.
Qual è la differenza tra INNER JOIN e LEFT JOIN?
INNER JOIN mantiene solo le righe che corrispondono su entrambi i lati. LEFT JOIN mantiene ogni riga della tabella di sinistra e riempie il lato destro con NULL dove non c'è corrispondenza.
Qual è la differenza tra RANK, DENSE_RANK e ROW_NUMBER?
Tutte e tre numerano le righe in un ordine. RANK dà lo stesso numero ai pari merito e salta i numeri successivi. DENSE_RANK dà lo stesso numero ai pari merito e non salta. ROW_NUMBER non ripete mai un numero.
Quale dialetto SQL usano queste risposte?
PostgreSQL. LIMIT/OFFSET, funzioni finestra, CTE e join funzionano quasi ovunque. In SQL Server usa TOP o OFFSET FETCH al posto di LIMIT, e in Oracle usa FETCH FIRST.

Esercitati