🧚 Ascolta! Beekeeper Studio è una GUI per database veloce, moderna e open source Scarica
7 gennaio 2026 Di Matthew Rathbone

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 keepers serve per MySQL, che altrimenti non permette a un DELETE di 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 BY o ROW_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:

Documentazione ufficiale:

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.

La versione Linux di Beekeeper è completa al 100%, senza tagli e senza compromessi sulle funzionalità.

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."
— Alex K., Database Developer
★★★★★
"Ho provato tante GUI per database, ma Beekeeper trova il perfetto equilibrio tra funzionalità e semplicità. Funziona e basta."
— Sarah M., Full Stack Engineer

Pronto a migliorare il tuo flusso di lavoro SQL?

download Scarica gratis