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

Per trovare i duplicati in SQL, raggruppa le righe in base alle colonne che dovrebbero essere univoche e tieni i gruppi con più di una riga. Le window function e i self-join ti mostrano poi le righe duplicate vere e proprie, così puoi decidere quale copia tenere. Gli esempi funzionano in PostgreSQL, MySQL, SQLite e SQL Server.

Come trovare i duplicati in SQL

GROUP BY mette insieme le righe con lo stesso valore, COUNT(*) conta ogni gruppo e HAVING tiene i gruppi con più di una riga:

SELECT columns, COUNT(*)
FROM table_name
GROUP BY columns
HAVING COUNT(*) > 1;

Per esempio, per trovare gli indirizzi email usati da più di un cliente:

-- Input: customers
-- id | name           | email
-- ---+----------------+----------------------
-- 1  | Maria Lopez    | maria.lopez@gmail.com
-- 2  | Sam Patel      | sam@patel.dev
-- 3  | M. Lopez       | maria.lopez@gmail.com
-- 4  | Kenji Watanabe | kenji@watanabe.jp

SELECT email, COUNT(*) AS copies
FROM customers
GROUP BY email
HAVING COUNT(*) > 1;

-- Output:
-- email                 | copies
-- ----------------------+-------
-- maria.lopez@gmail.com | 2

Per considerare duplicate le righe solo quando più colonne coincidono tutte, elencale tutte sia in SELECT sia in GROUP BY.

Regole da conoscere

  • Ogni colonna selezionata deve essere in GROUP BY o dentro un'aggregazione. Aggiungere name alla query qui sopra non mostra i nomi dei duplicati. È un errore, tranne in SQLite, che mostra un solo nome per email:
-- Input: the customers table above

SELECT name, email, COUNT(*) AS copies
FROM customers
GROUP BY email
HAVING COUNT(*) > 1;

-- ERROR: column "customers.name" must appear in the GROUP BY clause or be used in an aggregate function

Per vedere ogni riga duplicata, usa una delle query in “Sintassi alternativa”.

  • I NULL finiscono nello stesso gruppo e le maiuscole contano. Tutte le email NULL finiscono in un unico gruppo, quindi sembrano duplicate tra loro, mentre Ana@Silva.dev e ana@silva.dev no:
-- Input: users
-- id | email
-- ---+--------------
-- 1  | ana@silva.dev
-- 2  | NULL
-- 3  | Ana@Silva.dev
-- 4  | NULL

SELECT email, COUNT(*) AS copies
FROM users
GROUP BY email
HAVING COUNT(*) > 1;

-- Output:
-- email | copies
-- ------+-------
-- NULL  | 2

-- MySQL and SQL Server ignore case by default, so they also return ana@silva.dev | 2

-- Skip the NULLs and ignore case:
SELECT LOWER(email) AS email, COUNT(*) AS copies
FROM users
WHERE email IS NOT NULL
GROUP BY LOWER(email)
HAVING COUNT(*) > 1;

-- Output:
-- email         | copies
-- --------------+-------
-- ana@silva.dev | 2

Sintassi alternativa

GROUP BY restituisce una riga per ogni valore duplicato. Per ottenere le righe duplicate vere e proprie, conta invece su una window. COUNT(*) OVER (PARTITION BY email) riporta il conteggio di ogni email su ogni riga che la contiene:

-- Input: the customers table above

SELECT id, name, email, copies
FROM (
  SELECT id, name, email, COUNT(*) OVER (PARTITION BY email) AS copies
  FROM customers
) AS c
WHERE copies > 1
ORDER BY id;

-- Output:
-- id | name        | email                 | copies
-- ---+-------------+-----------------------+-------
-- 1  | Maria Lopez | maria.lopez@gmail.com | 2
-- 3  | M. Lopez    | maria.lopez@gmail.com | 2

EXISTS fa lo stesso senza window function: tiene ogni riga per cui esiste un'altra riga con la stessa email:

-- Input: the customers table above

SELECT id, name, email
FROM customers c
WHERE EXISTS (
  SELECT 1 FROM customers other
  WHERE other.email = c.email AND other.id <> c.id
);

-- Output:
-- id | name        | email
-- ---+-------------+----------------------
-- 1  | Maria Lopez | maria.lopez@gmail.com
-- 3  | M. Lopez    | maria.lopez@gmail.com

Quando conviene cercare i duplicati?

I duplicati entrano tramite import, doppi clic, tentativi ripetuti e vincoli mancanti. Ecco i quattro compiti che ti capiterà più spesso di dover affrontare.

Verificare che una colonna sia univoca

Prima di aggiungere un vincolo UNIQUE, o quando un login o una ricerca iniziano a restituire due righe, elenca i valori che compaiono più di una volta. GROUP BY con HAVING risponde direttamente.

Vedere tutte le righe coinvolte

Sapere quale valore si ripete non basta per sistemarlo: ti servono le righe complete per vedere in cosa differiscono e quale è quella giusta. Passa i valori duplicati a una query sulla stessa tabella.

Segnare le copie in eccesso

Per fare pulizia, tieni una riga per ogni gruppo di duplicati e rimuovi le altre. ROW_NUMBER() numera le righe all'interno di ogni gruppo, quindi tutto ciò che viene dopo la riga 1 è una copia in eccesso.

Individuare gli invii doppi

Un pulsante cliccato due volte o un pagamento ripetuto lasciano due righe identiche in tutto tranne che nell'ID. Unire la tabella con se stessa le accoppia, e t1.id < t2.id elenca ogni coppia una sola volta.

Esempi reali di ricerca dei duplicati

Trovare camere d'albergo prenotate due volte

Una camera può essere prenotata una sola volta per notte, quindi un duplicato è la stessa camera nella stessa notte. Raggruppa per entrambe le colonne:

-- Input: bookings
-- booking_id | room | night      | guest
-- -----------+------+------------+----------
-- 1          | 204  | 2026-11-06 | R. Okafor
-- 2          | 204  | 2026-11-07 | R. Okafor
-- 3          | 310  | 2026-11-06 | L. Chen
-- 4          | 204  | 2026-11-06 | P. Novak

SELECT room, night, COUNT(*) AS bookings
FROM bookings
GROUP BY room, night
HAVING COUNT(*) > 1;

-- Output:
-- room | night      | bookings
-- -----+------------+---------
-- 204  | 2026-11-06 | 2

Elencare gli account che condividono un indirizzo email

Capita che le persone si registrino due volte con lo stesso indirizzo scritto con maiuscole diverse. Partiziona per LOWER(email) per affiancare i due account:

-- Input: accounts
-- id | username | email
-- ---+----------+----------------
-- 1  | jkim     | jin@kim.kr
-- 2  | olivia_r | olivia@reyes.mx
-- 3  | jin.kim  | Jin@Kim.kr
-- 4  | noah_b   | noah@baker.uk

SELECT id, username, email, accounts_with_email
FROM (
  SELECT id, username, email,
         COUNT(*) OVER (PARTITION BY LOWER(email)) AS accounts_with_email
  FROM accounts
) AS a
WHERE accounts_with_email > 1
ORDER BY id;

-- Output:
-- id | username | email      | accounts_with_email
-- ---+----------+------------+--------------------
-- 1  | jkim     | jin@kim.kr | 2
-- 3  | jin.kim  | Jin@Kim.kr | 2

Segnalare le persone duplicate prima di una pulizia

Considera gli utenti con lo stesso nome e cognome come un'unica persona, tieni il record più vecchio ed etichetta gli altri. Una CTE tiene separata la numerazione dall'etichettatura:

-- Input: users
-- id | first_name | last_name | email
-- ---+------------+-----------+----------------
-- 1  | Priya      | Shah      | priya@shah.net
-- 2  | Tom        | Becker    | tom@becker.de
-- 3  | Priya      | Shah      | pshah@work.com
-- 4  | Lena       | Kowalski  | lena@kowal.pl
-- 5  | Tom        | Becker    | tbecker@mail.de

WITH numbered AS (
  SELECT id, first_name, last_name, email,
         ROW_NUMBER() OVER (PARTITION BY first_name, last_name ORDER BY id) AS copy_number
  FROM users
)
SELECT id, first_name, last_name, email,
       CASE WHEN copy_number > 1 THEN 'Duplicate' ELSE 'Original' END AS status
FROM numbered
ORDER BY id;

-- Output:
-- id | first_name | last_name | email           | status
-- ---+------------+-----------+-----------------+----------
-- 1  | Priya      | Shah      | priya@shah.net  | Original
-- 2  | Tom        | Becker    | tom@becker.de   | Original
-- 3  | Priya      | Shah      | pshah@work.com  | Duplicate
-- 4  | Lena       | Kowalski  | lena@kowal.pl   | Original
-- 5  | Tom        | Becker    | tbecker@mail.de | Duplicate

Le righe segnate come Duplicate sono quelle da unire o eliminare. La guida alla rimozione dei duplicati mostra come eliminarle.

Trovare ordini effettuati due volte per errore

Due ordini dello stesso cliente per lo stesso prodotto nello stesso giorno sono probabilmente un unico ordine inviato due volte:

-- Input: orders
-- order_id | customer_id | product   | order_date
-- ---------+-------------+-----------+-----------
-- 1001     | 42          | Desk Lamp | 2026-03-02
-- 1002     | 42          | Desk Lamp | 2026-03-02
-- 1003     | 42          | Desk Lamp | 2026-03-09
-- 1004     | 57          | Desk Lamp | 2026-03-02

SELECT o1.order_id AS first_order, o2.order_id AS repeat_order,
       o1.customer_id, o1.product
FROM orders o1
JOIN orders o2
  ON o1.customer_id = o2.customer_id
 AND o1.product = o2.product
 AND o1.order_date = o2.order_date
 AND o1.order_id < o2.order_id;

-- Output:
-- first_order | repeat_order | customer_id | product
-- ------------+--------------+-------------+----------
-- 1001        | 1002         | 42          | Desk Lamp

Approfondimenti sui 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 molte 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