Rimuovere i duplicati in SQL significa una di due cose: nascondere le righe ripetute dal risultato di una query con DISTINCT, oppure eliminare le copie in eccesso dalla tabella in modo che ne resti una per ciascuna. Gli esempi funzionano in PostgreSQL, MySQL, SQLite e SQL Server.
Come rimuovere i duplicati in SQL
SELECT DISTINCT restituisce ogni combinazione delle colonne elencate una sola volta e lascia intatta la tabella. Per ripulire la tabella stessa, elimina tutte le righe tranne una in ogni gruppo di duplicati, di solito quella con l'id più basso:
SELECT DISTINCT column_list FROM table_name;
DELETE FROM table_name
WHERE id NOT IN (
SELECT id FROM (
SELECT MIN(id) AS id FROM table_name GROUP BY column_list
) AS keepers
);
Qui Maria Lopez è stata inserita tre volte:
-- Input: customers
-- id | name | email | phone
-- ---+-------------+-------------------+---------
-- 1 | Maria Lopez | maria@example.com | 555-0142
-- 2 | Sam Patel | sam@example.com | 555-0178
-- 3 | Maria Lopez | maria@example.com | 555-0142
-- 4 | Lena Novak | lena@example.com | 555-0121
-- 5 | Maria Lopez | maria@example.com | 555-0142
SELECT DISTINCT name, email, phone
FROM customers
ORDER BY name;
-- Output:
-- name | email | phone
-- ------------+-------------------+---------
-- Lena Novak | lena@example.com | 555-0121
-- Maria Lopez | maria@example.com | 555-0142
-- Sam Patel | sam@example.com | 555-0178
DELETE FROM customers
WHERE id NOT IN (
SELECT id FROM (
SELECT MIN(id) AS id FROM customers GROUP BY name, email, phone
) AS keepers
);
-- Output (customers after the DELETE):
-- id | name | email | phone
-- ---+-------------+-------------------+---------
-- 1 | Maria Lopez | maria@example.com | 555-0142
-- 2 | Sam Patel | sam@example.com | 555-0178
-- 4 | Lena Novak | lena@example.com | 555-0121
Regole da conoscere
-
Elenca le colonne che rendono due righe dei duplicati. Escludi l'id e tutto ciò che è sempre diverso, come un timestamp
created_at. Le righe che differiscono in una qualsiasi delle colonne elencate vengono tutte mantenute. -
Il wrapper
SELECT id FROM (...) AS keepersserve per MySQL, che altrimenti non permette a unDELETEdi leggere la propria tabella in una subquery: “You can’t specify target table ‘customers’ for update in FROM clause”. -
Controlla prima di eliminare. Esegui prima la query con
GROUP BYoROW_NUMBER()per vedere cosa verrà rimosso, e fai l'eliminazione dentro una transazione di cui puoi fare il rollback. - Blocca i nuovi duplicati con un indice univoco una volta ripulita la tabella:
CREATE UNIQUE INDEX customers_email_key ON customers (email);
INSERT INTO customers VALUES (6, 'Maria Lopez', 'maria@example.com', '555-0142');
-- ERROR: duplicate key value violates unique constraint "customers_email_key"
Sintassi alternativa
Nel codice SQL Server vedrai spesso le copie numerate in una CTE ed eliminate tramite essa, cosa che solo SQL Server consente. Sulla tabella customers originale:
-- SQL Server
WITH numbered AS (
SELECT *, ROW_NUMBER() OVER (
PARTITION BY name, email, phone ORDER BY id
) AS row_num
FROM customers
)
DELETE FROM numbered WHERE row_num > 1;
-- Output (customers after the DELETE):
-- id | name | email | phone
-- ---+-------------+-------------------+---------
-- 1 | Maria Lopez | maria@example.com | 555-0142
-- 2 | Sam Patel | sam@example.com | 555-0178
-- 4 | Lena Novak | lena@example.com | 555-0121
Per combinare due query senza ripetere righe, usa UNION, che rimuove i duplicati, invece di UNION ALL, che li mantiene:
SELECT 'apple' AS fruit UNION ALL SELECT 'apple';
-- Output:
-- fruit
-- -----
-- apple
-- apple
SELECT 'apple' AS fruit UNION SELECT 'apple';
-- Output: apple
Quando conviene rimuovere i duplicati?
A volte devi solo nascondere le ripetizioni in un risultato, altre volte è la tabella stessa a dover essere ripulita. Questi sono i passaggi tipici, dal controllo all'eliminazione.
Mostrare ogni valore una volta
Per un report, un menu a tendina o un rapido controllo di cosa c'è in una colonna, SELECT DISTINCT elimina le ripetizioni dal risultato e lascia la tabella com'è.
Contare le copie prima di eliminare
Raggruppa per le colonne che definiscono un duplicato e tieni i gruppi con HAVING COUNT(*) > 1. Così vedi cosa è duplicato e quante volte, prima di rimuovere qualsiasi cosa. La guida per trovare i duplicati in SQL mostra altri modi per cercarli.
Scegliere quale copia tenere
ROW_NUMBER() numera le righe in ogni gruppo di duplicati. L'ORDER BY dentro OVER (...) decide quale riga riceve 1 e resta: la più vecchia, la più recente o la più completa.
Eliminare le copie in eccesso
Una volta deciso quale riga tenere, elimina le altre. Per tenere la riga più recente invece della più vecchia, numera le righe per data, dalla più recente, ed elimina tutte quelle con numero maggiore di 1.
Esempi reali di rimozione dei duplicati
Tutti i paesi e i corrieri con cui spedisci
DISTINCT si applica all'intera riga di colonne selezionate, quindi il Canada compare una volta per ogni corriere:
-- Input: shipments
-- order_id | country | carrier
-- ---------+---------+--------
-- 1001 | Canada | UPS
-- 1002 | Germany | DHL
-- 1003 | Canada | FedEx
-- 1004 | Canada | UPS
-- 1005 | Germany | DHL
SELECT DISTINCT country, carrier
FROM shipments
ORDER BY country, carrier;
-- Output:
-- country | carrier
-- --------+--------
-- Canada | FedEx
-- Canada | UPS
-- Germany | DHL
Indirizzi email che differiscono solo per maiuscole/minuscole
Raggruppa per l'email in minuscolo così che Jon@Example.com e jon@example.com contino come copie. (MySQL e SQL Server ignorano già maiuscole e minuscole per impostazione predefinita, e lì LOWER() non fa danni.)
-- Input: accounts
-- id | email
-- ---+----------------------
-- 1 | Ana.Silva@example.com
-- 2 | jon@example.com
-- 3 | ana.silva@example.com
-- 4 | Jon@Example.com
-- 5 | mei@example.com
SELECT LOWER(email) AS email, COUNT(*) AS copies
FROM accounts
GROUP BY LOWER(email)
HAVING COUNT(*) > 1
ORDER BY email;
-- Output:
-- email | copies
-- ----------------------+-------
-- ana.silva@example.com | 2
-- jon@example.com | 2
Tenere la copia che ha un numero di telefono
Ordina prima le righe con un telefono, poi per id, così la riga più completa di ogni gruppo è la numero 1:
-- Input: contacts
-- id | email | phone
-- ---+------------------+---------
-- 1 | ravi@example.com | NULL
-- 2 | ravi@example.com | 555-0110
-- 3 | ines@example.com | 555-0133
-- 4 | ines@example.com | NULL
SELECT
id,
email,
phone,
ROW_NUMBER() OVER (
PARTITION BY email
ORDER BY CASE WHEN phone IS NULL THEN 1 ELSE 0 END, id
) AS row_num
FROM contacts
ORDER BY email, row_num;
-- Output:
-- id | email | phone | row_num
-- ---+------------------+----------+--------
-- 3 | ines@example.com | 555-0133 | 1
-- 4 | ines@example.com | NULL | 2
-- 2 | ravi@example.com | 555-0110 | 1
-- 1 | ravi@example.com | NULL | 2
Eliminando le righe con numero 2 o superiore (id 1 e 4) resta un contatto completo per ogni email.
Eliminare copie esatte da una tabella senza id
Quando le copie sono identiche in ogni colonna, non c'è un id di cui tenere il più basso. Copia le righe distinte in una nuova tabella e sostituiscila all'originale. Ricrea gli indici e i vincoli che aveva la vecchia tabella, perché la copia non li mantiene:
-- Input: page_views
-- url | viewed_at
-- ---------+--------------------
-- /pricing | 2026-10-01 09:00:00
-- /pricing | 2026-10-01 09:00:00
-- /docs | 2026-10-01 09:05:00
-- /pricing | 2026-10-01 09:00:00
-- In SQL Server, use SELECT DISTINCT * INTO page_views_clean FROM page_views,
-- then EXEC sp_rename 'page_views_clean', 'page_views'
CREATE TABLE page_views_clean AS SELECT DISTINCT * FROM page_views;
DROP TABLE page_views;
ALTER TABLE page_views_clean RENAME TO page_views;
-- Output (page_views afterwards):
-- url | viewed_at
-- ---------+--------------------
-- /pricing | 2026-10-01 09:00:00
-- /docs | 2026-10-01 09:05:00
Approfondimenti sulla rimozione dei duplicati
Su questo blog:
- Guida per trovare i duplicati in SQL: altri modi per individuare le righe duplicate prima di rimuoverle
- Individuare righe duplicate in una tabella SQL Server: il punto di vista di SQL Server
- Guida a SQL COUNT DISTINCT: contare i valori unici invece di rimuovere le ripetizioni
- Sintassi ed esempi dell'istruzione SQL DELETE: le basi dell'eliminazione delle righe
- Creare vincoli univoci in PostgreSQL: tenere lontani i duplicati per sempre
- Guida SQL CTE vs subquery: i due modi di scrivere il passaggio “quali righe tenere”
Documentazione ufficiale:
- PostgreSQL: DELETE e window function
- MySQL: DELETE e ROW_NUMBER()
- SQLite: DELETE e window function
- SQL Server: DELETE e ROW_NUMBER
Beekeeper Studio è una GUI per database gratuita & open source
Il miglior strumento per query & editor SQL che abbia mai usato. Offre tutto ciò che mi serve per gestire il mio database. - ⭐⭐⭐⭐⭐ Mit
Beekeeper Studio è veloce, intuitivo e facile da usare. Beekeeper supporta tantissimi database e funziona alla grande su Windows, Mac e Linux.
Cosa dicono gli utenti di Beekeeper Studio
"Beekeeper Studio ha sostituito completamente il mio vecchio flusso di lavoro SQL. È veloce, intuitivo e rende di nuovo piacevole lavorare con i database."
"Ho provato tante GUI per database, ma Beekeeper trova il perfetto equilibrio tra funzionalità e semplicità. Funziona e basta."