Vai al contenuto principale

Ottimizzazione delle query SQL: 15 tecniche per prestazioni migliori

Esplora diverse strategie per query SQL più veloci ed efficienti.
Aggiornato 16 apr 2026  · 14 min leggi

SQL è uno strumento fondamentale per chiunque gestisca e manipoli dati all'interno di database relazionali. Ci permette di interagire con i database ed eseguire attività essenziali in modo efficiente. Con la quantità di dati disponibili in crescita ogni giorno, ci troviamo ad affrontare la sfida di scrivere query complesse per recuperarli.

Le query lente possono essere un vero collo di bottiglia, con impatti che vanno dalle prestazioni dell’applicazione all’esperienza utente. Ottimizzare le query SQL migliora le prestazioni, riduce il consumo di risorse e garantisce scalabilità.

In questo articolo vedremo alcune delle tecniche più efficaci per ottimizzare le nostre query SQL. Approfondiremo vantaggi e svantaggi di ciascuna tecnica per capirne l’impatto sulle prestazioni delle query. Iniziamo!

1. Usa un'indicizzazione appropriata

Immagina di cercare un libro in una biblioteca senza catalogo. Dovresti controllare ogni scaffale e ogni ripiano finché non lo trovi. Gli indici in un database sono simili ai cataloghi. Ci aiutano a trovare rapidamente i dati di cui abbiamo bisogno senza scansionare l’intera tabella.

Come funzionano gli indici

Gli indici sono strutture dati che migliorano la velocità di recupero dei dati. Creano una copia ordinata delle colonne indicizzate, consentendo al database di individuare rapidamente le righe che corrispondono alla nostra query, facendoci risparmiare molto tempo. 

Esistono tre tipi principali di indici nei database: 

  • Indici cluster - Ordinano fisicamente i dati in base ai valori delle colonne e sono ideali per dati sequenziali o ordinati senza duplicati, come le chiavi primarie.
  • Indici non cluster - Creano due strutture separate, risultando adatti per tabelle di mappatura o glossari. 
  • Indici full-text - Utilizzati per cercare in grandi campi di testo, come articoli o email, memorizzando le posizioni dei termini all’interno del testo.

Come possiamo usare gli indici per migliorare le prestazioni delle query SQL? Vediamo alcune best practice:

  • Indicizza le colonne interrogate più spesso. Se di solito cerchiamo in una tabella usando customer_id o item_id, indicizzare queste colonne avrà un grande impatto sulla velocità. Di seguito come creare un indice:
CREATE INDEX index_customer_id ON customers (customer_id);
  • Evita indici non necessari. Anche se gli indici sono molto utili per velocizzare le query SELECT, possono rallentare leggermente le operazioni INSERT, UPDATE e DELETE. Questo perché l’indice va aggiornato ogni volta che modifichi i dati. Troppi indici possono quindi rallentare le cose aumentando l’overhead sulle modifiche ai dati. 
  • Scegli il tipo di indice giusto. I diversi database offrono vari tipi di indici. Dovremmo scegliere quello che meglio si adatta ai nostri dati e ai pattern di query. Ad esempio, un indice B-tree è una buona scelta se cerchiamo spesso intervalli di valori.

2. Evita SELECT *

A volte siamo tentati di usare SELECT * per prendere tutte le colonne, anche quelle che non sono rilevanti per la nostra analisi. Anche se può sembrare comodo, porta a query molto inefficienti che possono rallentare le prestazioni. 

Il database deve leggere e trasferire più dati del necessario, richiedendo più memoria poiché il server deve elaborare e memorizzare più informazioni del dovuto.

Come regola generale, dovremmo selezionare solo le colonne specifiche di cui abbiamo bisogno. Ridurre i dati superflui non solo mantiene il codice pulito e facile da capire, ma aiuta anche a ottimizzare le prestazioni.

Quindi, invece di scrivere:

SELECT * 
FROM products;

Dovremmo scrivere:

SELECT product_id, product_name, product_price 
FROM products;

3. Evita il recupero di dati ridondanti o non necessari

Abbiamo appena detto che selezionare solo le colonne rilevanti è una best practice per ottimizzare le query SQL. Tuttavia, è anche importante limitare il numero di righe che stiamo recuperando, non solo le colonne. Le query di solito rallentano quando aumenta il numero di righe. 

Possiamo usare LIMIT per ridurre il numero di righe restituite. Questa funzionalità impedisce di recuperare involontariamente migliaia di righe quando ci serve lavorare solo con poche. 

La funzione LIMIT è particolarmente utile per query di validazione o per ispezionare l’output di una trasformazione su cui stiamo lavorando. È ideale per sperimentare e capire come si comporta il nostro codice. Tuttavia, potrebbe non essere adatta a modelli di dati automatizzati, dove dobbiamo restituire l’intero dataset. 

Ecco un esempio di come funziona LIMIT:

SELECT name 
FROM customers 
ORDER BY customer_group DESC 
LIMIT 100;

4. Usa i join in modo efficiente

Quando lavoriamo con database relazionali, i dati sono spesso organizzati in tabelle separate per evitare ridondanze e migliorare l’efficienza. Questo significa però che dobbiamo recuperare dati da punti diversi e unirli per ottenere tutte le informazioni rilevanti di cui abbiamo bisogno.  

I join ci permettono di combinare, in un’unica query, righe provenienti da due o più tabelle in base a una colonna correlata tra di esse, rendendo possibili analisi più complesse.

Esistono diversi tipi di join e dobbiamo sapere come usarli. Usare il join sbagliato può creare duplicati nel dataset e rallentarlo.

  • Un inner join restituisce solo le righe con una corrispondenza in entrambe le tabelle. Se un record esiste in una tabella ma non nell’altra, verrà escluso dal risultato.

an image showing three tables. The two on the left are the original tables. The one at the right is the result of the inner join. Showing only rows that are common in the left tables.

Figura: Inner Join. Fonte immagine: DataCamp SQL-Join cheat sheet.

SELECT o.order_id, c.name
FROM orders o
INNER JOIN customers c ON o.customer_id = c.customer_id;
  • Un outer join restituisce tutte le righe di una tabella e le righe corrispondenti dell’altra. Se non c’è corrispondenza, vengono restituiti valori NULL per le colonne della tabella senza riga corrispondente. 

an image showing three tables. The two on the left are the original tables. The one at the right is the result of the outer join. Showing all rows in both left tables.

Figura: Outer o Full Join. Fonte immagine: DataCamp SQL-Join cheat sheet.

SELECT o.order_id, c.name
FROM orders o
FULL OUTER JOIN customers c ON o.customer_id = c.customer_id;
  • Un left join include tutte le righe della tabella di sinistra e le righe corrispondenti della tabella di destra. Se non viene trovata alcuna corrispondenza, vengono restituiti valori NULL per le colonne della tabella di destra. 
  • Allo stesso modo, un right join include tutte le righe della tabella di destra, con le righe corrispondenti della sinistra, riempiendo con NULL dove non esiste corrispondenza.

an image showing three tables at the top and three tables at the bottom. The two on the left are the original tables. The one at the right is the result of the right or left join. Showing rows that are common on both tables or that are in the left/right table.

an image showing three tables at the top and three tables at the bottom. The two on the left are the original tables. The one at the right is the result of the right or left join. Showing rows that are common on both tables or that are in the left/right table.

Figura: Left e Right Join. Fonte immagine: DataCamp SQL-Join cheat sheet.

SELECT c.name, o.order_id
FROM customers c
LEFT JOIN orders o ON c.customer_id = o.customer_id;

Consigli per join efficienti:

  • Ordina i join in modo logico. Dovremmo iniziare dalle tabelle che restituiscono meno righe. Questo riduce la quantità di dati da elaborare nei join successivi.
  • Usa indici sulle colonne di join. Ancora una volta, gli indici sono nostri alleati. Usarli aiuta il database a trovare rapidamente le righe corrispondenti.
  • Valuta l’uso di sottoquery o CTE (Common Table Expressions) per semplificare join complessi:
WITH RecentOrders AS (
    SELECT customer_id, order_id
    FROM orders
    WHERE order_date >= DATE('now', '-30 days') 
)
SELECT c.customer_name, ro.order_id
FROM customers c
INNER JOIN RecentOrders ro ON c.customer_id = ro.customer_id;

5. Analizza i piani di esecuzione delle query

Molto spesso eseguiamo query SQL e controlliamo solo che l’output o il risultato recuperato sia quello atteso. Raramente però ci chiediamo cosa succede dietro le quinte quando eseguiamo una query SQL.

La maggior parte dei database fornisce funzioni come EXPLAIN o EXPLAIN PLAN per visualizzare questo processo. Questi piani offrono una scomposizione passo passo di come il database recupererà i dati. Possiamo usare questa funzione per identificare i colli di bottiglia delle prestazioni e prendere decisioni informate su come ottimizzare le query.

Vediamo come usare EXPLAIN per individuare i colli di bottiglia. Eseguiremo il seguente codice:

EXPLAIN SELECT f.title, a.actor_name
FROM film f, film_actor fa,  actor a
WHERE f.film_id = fa.film_id and fa.actor_id = a.id 

Possiamo quindi esaminare i risultati:

an image showing an execution plan from DBeaver that displays information on joins, and the query.

Figura: Un esempio di piano di esecuzione di una query. Fonte immagine: sito CloudDBeaver.

Ecco alcune linee guida generali per interpretare i risultati:

  • Full table scan: Se il piano mostra una scansione completa della tabella, il database analizza ogni riga, con possibili tempi molto lunghi. Spesso indica un indice mancante o una clausola WHERE inefficiente.
  • Strategie di join inefficienti: Il piano può rivelare se il database usa un algoritmo di join poco ottimale.
  • Altri potenziali problemi: I piani di explain possono evidenziare altri problemi, come costi di ordinamento elevati o uso eccessivo di tabelle temporanee.

6. Ottimizza le clausole WHERE

La clausola WHERE è fondamentale nelle query SQL perché ci permette di filtrare i dati in base a condizioni specifiche, assicurando che vengano restituite solo le righe rilevanti. Migliora l’efficienza delle query riducendo la quantità di dati elaborati, aspetto molto importante quando si lavora con dataset di grandi dimensioni. 

Una WHERE ben scritta può quindi essere un’alleata potente quando ottimizziamo le prestazioni di una query SQL. Vediamo alcuni modi per sfruttarla al meglio:

  • Aggiungi condizioni di filtro adeguate il prima possibile. A volte avere una clausola WHERE è utile ma non basta. Bisogna fare attenzione a dove viene posizionata. Filtrare il maggior numero possibile di righe all’inizio della WHERE può aiutare a ottimizzare la query.
  • Evita di usare funzioni sulle colonne nella clausola WHERE. Quando applichiamo una funzione a una colonna, il database deve applicarla a ogni riga della tabella prima di poter filtrare i risultati. Questo impedisce l’uso efficace degli indici.

Per esempio, invece di: 

SELECT * 
FROM employees WHERE 
YEAR(hire_date) = 2020;

Dovremmo usare: 

SELECT * 
FROM employees 
WHERE hire_date >= '2020-01-01' AND hire_date < '2021-01-01';
  • Usa operatori appropriati. Dovremmo scegliere gli operatori più efficienti che soddisfano le nostre esigenze. Ad esempio, = è generalmente più veloce di LIKE e usare intervalli di date specifici è più veloce che usare funzioni come MONTH(order_date).

Quindi, ad esempio, invece di eseguire questa query:

SELECT * 
FROM orders 
WHERE MONTH(order_date) = 12 AND YEAR(order_date) = 2023;

Possiamo eseguire la seguente: 

SELECT * 
FROM orders 
WHERE order_date >= '2023-12-01' AND order_date < '2024-01-01';

7. Ottimizza le sottoquery

In alcuni casi, mentre scriviamo una query, sentiamo il bisogno di applicare dinamicamente filtri, aggregazioni o join sui dati. Non vogliamo fare più query; preferiamo mantenerne una sola. 

Per questi casi possiamo usare le sottoquery. Le sottoquery in SQL sono query annidate all’interno di un’altra query, in genere nelle istruzioni SELECT, INSERT, UPDATE o DELETE

Le sottoquery possono essere potenti e veloci, ma possono anche causare problemi di prestazioni se non usate con attenzione. In generale, dovremmo minimizzare l’uso delle sottoquery e seguire alcune best practice:

  • Sostituisci le sottoquery con join quando possibile. I join sono generalmente più veloci e più efficienti delle sottoquery.
  • Usa le common table expressions (CTE) al loro posto.  Le CTE separano il nostro codice in alcune query più piccole anziché in una unica e grande, risultando molto più leggibili.
WITH SalesCTE AS ( 
             SELECT salesperson_id, SUM(sales_amount) AS total_sales 
             FROM sales GROUP BY salesperson_id ) 

SELECT salesperson_id, total_sales 
FROM SalesCTE WHERE total_sales > 5000;
  • Usa sottoquery non correlate. Le sottoquery non correlate sono indipendenti dalla query esterna e possono essere eseguite una sola volta, mentre quelle correlate vengono eseguite per ogni riga della query esterna.

8. Usa EXISTS invece di IN per le sottoquery

Quando lavoriamo con sottoquery, spesso dobbiamo verificare se un valore esiste in un insieme di risultati. Possiamo farlo con IN o EXISTS, ma EXISTS è in genere più efficiente, soprattutto su dataset di grandi dimensioni.

La clausola IN legge l’intero risultato della sottoquery in memoria prima del confronto. Al contrario, EXISTS interrompe l’elaborazione della sottoquery non appena trova una corrispondenza. 

Ecco un esempio di come usare questa clausola:

SELECT * 
FROM orders o
WHERE EXISTS (SELECT 1 FROM customers c WHERE c.customer_id = o.customer_id AND c.country = 'USA');

9. Limita l'uso di DISTINCT

Immagina di lavorare a un’analisi per inviare un’offerta promozionale a clienti di città uniche. Il database contiene più ordini degli stessi clienti. La prima cosa che ci viene in mente è usare la clausola DISTINCT.

Questa funzione è utile in certi casi ma può essere dispendiosa in termini di risorse, soprattutto su dataset grandi. Esistono alcune alternative a DISTINCT:

  • Identificare e rimuovere i dati duplicati durante i processi di pulizia. Questo previene la comparsa di duplicati nel database all’origine.
  • Usare GROUP BY al posto di DISTINCT quando possibile. GROUP BY può essere più efficiente, soprattutto se combinato con funzioni di aggregazione. 

Quindi, invece di eseguire:

SELECT DISTINCT city FROM customers;

Possiamo usare:

SELECT city FROM customers GROUP BY city;
  • Usa funzioni finestra. Funzioni finestra come ROW_NUMBER possono aiutarci a identificare i duplicati e filtrarli senza usare DISTINCT.

10. Sfrutta le funzionalità specifiche del database

Quando lavoriamo con i dati, interagiamo tramite SQL attraverso un sistema di gestione del database (DBMS). Il DBMS elabora i comandi SQL, gestisce il database e garantisce integrità e sicurezza dei dati. I diversi sistemi di database offrono funzionalità uniche che possono aiutare a ottimizzare le query. 

Gli hint del database sono istruzioni speciali che possiamo aggiungere alle query per eseguirle in modo più efficiente. Sono uno strumento utile, ma vanno usati con cautela. 

Ad esempio, in MySQL, l’hint USE INDEX può forzare l’uso di un indice specifico:

 SELECT * FROM employees USE INDEX (idx_salary) WHERE salary > 50000;

In SQL Server, l’hint OPTION (LOOP JOIN) specifica il metodo di join: 

SELECT * 
FROM orders 
INNER JOIN customers ON orders.customer_id = customers.id OPTION (LOOP JOIN); 

Questi hint sovrascrivono l’ottimizzazione predefinita delle query, migliorando le prestazioni in scenari specifici.

D’altra parte, partizionamento e sharding sono due tecniche per distribuire i dati nel cloud. 

  • Con il partizionamento, dividiamo una grande tabella in più tabelle più piccole, ciascuna con la propria chiave di partizione. Le chiavi sono in genere basate sui timestamp di creazione delle righe o su valori interi. Quando eseguiamo una query su questa tabella, il server ci indirizza automaticamente alla partizione appropriata. 
  • Lo sharding è molto simile, tranne per il fatto che invece di suddividere una grande tabella in tabelle più piccole, si divide un grande database in database più piccoli. Ognuno di questi database è su un server diverso. Invece di una chiave di partizione, una chiave di sharding reindirizza le query verso il database appropriato. Lo sharding aumenta la velocità di elaborazione perché il carico è suddiviso su server diversi. 

11. Monitora e ottimizza le statistiche del database

Mantenere aggiornate le statistiche del database è importante per fare in modo che l’ottimizzatore di query possa prendere decisioni informate e accurate sul modo più efficiente di eseguire le query. 

Le statistiche descrivono la distribuzione dei dati in una tabella (ad esempio, il numero di righe, la frequenza dei valori e la loro distribuzione tra le colonne) e l’ottimizzatore si basa su queste informazioni per stimare i costi di esecuzione. Se le statistiche non sono aggiornate, l’ottimizzatore può scegliere piani di esecuzione inefficienti, come usare gli indici sbagliati o optare per una scansione completa invece di una più efficiente scansione tramite indice, con conseguenti scarse prestazioni delle query.

I database spesso supportano aggiornamenti automatici per mantenere statistiche accurate. Ad esempio, in SQL Server, la configurazione predefinita aggiorna automaticamente le statistiche quando cambia una quantità significativa di dati. Allo stesso modo, PostgreSQL ha una funzionalità di auto-analyze, che aggiorna le statistiche dopo una soglia specificata di modifiche ai dati. 

Tuttavia, possiamo aggiornare manualmente le statistiche nei casi in cui gli aggiornamenti automatici non bastano o se è necessario un intervento manuale. In SQL Server, possiamo usare il comando UPDATE STATISTICS per aggiornare le statistiche di una specifica tabella o indice, mentre in PostgreSQL si può eseguire il comando ANALYZE per aggiornare le statistiche di una o più tabelle. 

-- Update statistics for all tables in the current database
ANALYZE;

-- Update statistics for a specific table
ANALYZE my_table;

12. Usa stored procedure

Una stored procedure è un insieme di comandi SQL che salviamo nel database, così non dobbiamo riscrivere ogni volta lo stesso SQL. Possiamo pensarla come a uno script riutilizzabile. 

Quando dobbiamo eseguire un certo compito, come aggiornare record o calcolare valori, richiamiamo la stored procedure. Può accettare input, eseguire operazioni come interrogare o modificare dati e persino restituire un risultato. Le stored procedure accelerano le operazioni poiché l’SQL è precompilato, rendendo il codice più pulito e facile da gestire. 

Possiamo creare una stored procedure in PostgreSQL come segue:

CREATE OR REPLACE PROCEDURE insert_employee(
    emp_id INT,
    emp_first_name VARCHAR,
    emp_last_name VARCHAR
)
LANGUAGE plpgsql
AS $
BEGIN
    -- Insert a new employee into the employees table
    INSERT INTO employees (employee_id, first_name, last_name)
    VALUES (emp_id, emp_first_name, emp_last_name);
END;
$;

-- call the procedure
CALL insert_employee(101, 'John', 'Doe');

13. Evita ordinamenti e raggruppamenti non necessari

Come professionisti dei dati, ci piace avere i dati ordinati e raggruppati per ricavarne insight più facilmente. In SQL usiamo spesso ORDER BY e GROUP BY.

Tuttavia, entrambe le clausole possono essere costose dal punto di vista computazionale, soprattutto con dataset grandi. Quando ordina o aggrega i dati, il motore del database spesso deve eseguire una scansione completa e poi organizzarli, identificare i gruppi e/o applicare funzioni di aggregazione, tipicamente usando algoritmi ad alta intensità di risorse. 

Per ottimizzare le query, possiamo seguire alcuni suggerimenti:

  • Minimizza l’ordinamento. Dovremmo usare ORDER BY solo quando necessario. Se l’ordinamento non è essenziale, omettere questa clausola può ridurre drasticamente i tempi di elaborazione. 
  • Usa gli indici. Quando possibile, assicuriamoci che le colonne coinvolte in ORDER BY e GROUP BY siano indicizzate. 
  • Sposta l’ordinamento nel livello applicativo. Se possibile, spostiamo l’operazione di ordinamento al livello applicativo anziché nel database. 
  • Pre-aggrega i dati. Per query complesse che coinvolgono GROUP BY, possiamo pre-aggregare i dati in una fase precedente o in una vista materializzata, in modo che il database non debba ricalcolare ogni volta le stesse aggregazioni.

14. Usa UNION ALL invece di UNION

Quando vogliamo combinare i risultati di più query in un unico elenco, possiamo usare le clausole UNION e UNION ALL. Entrambe combinano i risultati di due o più istruzioni SELECT con gli stessi nomi di colonna. Tuttavia, non sono identiche e la loro differenza le rende adatte a casi d’uso diversi.

La clausola UNION rimuove le righe duplicate, cosa che richiede più tempo di elaborazione. 

an image showing three tables at the top and three tables at the bottom. The two on the left are the original tables. The one at the right is the result of the union. Showing resulting rows without duplicates.

Figura: Union in SQL. Source immagine: DataCamp SQL-Join cheat sheet.

D’altra parte, UNION ALL combina i risultati ma mantiene tutte le righe, inclusi i duplicati. Quindi, se non serve rimuovere duplicati, è meglio usare UNION ALL per prestazioni migliori.

an image showing three tables at the top and three tables at the bottom. The two on the left are the original tables. The one at the right is the result of the union all. Showing resulting rows with duplicates.

Figura: UNION ALL in SQL. Source immagine: DataCamp SQL-Join cheat sheet.

-- Potentially slower
SELECT product_id FROM products WHERE category = 'Electronics'
UNION
SELECT product_id FROM products WHERE category = 'Books';

-- Potentially faster
SELECT product_id FROM products WHERE category = 'Electronics'
UNION ALL
SELECT product_id FROM products WHERE category = 'Books';

15. Scomponi le query complesse

Lavorare con dataset di grandi dimensioni implica che spesso incontreremo query complesse difficili da comprendere e ottimizzare. Possiamo affrontare questi casi scomponendole in query più piccole e semplici. In questo modo è più facile individuare i colli di bottiglia e applicare tecniche di ottimizzazione.

Una delle strategie più usate per scomporre le query è ricorrere alle viste materializzate. Si tratta di risultati di query precomputati e memorizzati, accessibili rapidamente senza ricalcolare la query ogni volta che viene richiamata. Quando i dati sottostanti cambiano, la vista materializzata deve essere aggiornata manualmente o automaticamente.

Ecco un esempio di come creare e interrogare una vista materializzata:

-- Create a materialized view
CREATE MATERIALIZED VIEW daily_sales AS
SELECT product_id, SUM(quantity) AS total_quantity
FROM order_items
GROUP BY product_id;

-- Query the materialized view
SELECT * FROM daily_sales;

Conclusione

In questo articolo abbiamo esplorato diverse strategie e best practice per ottimizzare le query SQL, dall’indicizzazione ai join, fino a sottoquery e funzionalità specifiche dei database. Applicando queste tecniche, puoi migliorare sensibilmente le prestazioni delle tue query e far funzionare i database in modo più efficiente.

Ricorda che ottimizzare le query SQL è un processo continuo. Man mano che i tuoi dati crescono e l’applicazione evolve, dovrai monitorare e ottimizzare costantemente le query per garantire prestazioni ottimali.

Per approfondire la tua conoscenza di SQL, ti invitiamo a esplorare le seguenti risorse su DataCamp:


Maria Eugenia Inzaugarat's photo
Author
Maria Eugenia Inzaugarat
Argomenti

Approfondisci SQL con questi corsi!

Corso

Manipolazione dei dati in SQL

4 h
317K
Vedi dettagliRight Arrow
Inizia il corso
Mostra altroRight Arrow
Correlato

blog

I 15 migliori server MCP remoti che ogni AI builder dovrebbe conoscere nel 2026

Scopri i 15 migliori server MCP remoti che stanno trasformando lo sviluppo AI nel 2026. Scopri come migliorano automazione, ragionamento, sicurezza e velocità dei workflow.
Abid Ali Awan's photo

Abid Ali Awan

15 min

blog

Che cos'è Snowflake? Guida per principianti alla piattaforma dati cloud

Esplora le basi di Snowflake, la piattaforma dati cloud. Scopri la sua architettura, le sue funzionalità e come integrarla nelle tue pipeline di dati.
Tim Lu's photo

Tim Lu

12 min

blog

Tokenizzazione nel NLP: come funziona, sfide e casi d'uso

Guida al preprocessing NLP nel machine learning. Copriamo spaCy, i transformer di Hugging Face e come funziona la tokenizzazione in casi d'uso reali.
Abid Ali Awan's photo

Abid Ali Awan

10 min

Mostra altroMostra altro