Για να βρεις διπλότυπα στην SQL, ομαδοποίησε τις γραμμές με βάση τις στήλες που πρέπει να είναι μοναδικές και κράτα τις ομάδες με περισσότερες από μία γραμμές. Στη συνέχεια, οι window functions και τα self-joins σου δείχνουν τις ίδιες τις διπλότυπες γραμμές, ώστε να αποφασίσεις ποιο αντίγραφο θα κρατήσεις. Τα παραδείγματα δουλεύουν σε PostgreSQL, MySQL, SQLite και SQL Server.
Πώς να βρεις διπλότυπα στην SQL
Το GROUP BY βάζει μαζί τις γραμμές με την ίδια τιμή, το COUNT(*) μετράει κάθε ομάδα και το HAVING κρατάει τις ομάδες με περισσότερες από μία γραμμές:
SELECT columns, COUNT(*)
FROM table_name
GROUP BY columns
HAVING COUNT(*) > 1;
Για παράδειγμα, για να βρεις διευθύνσεις email που χρησιμοποιούνται από περισσότερους από έναν πελάτες:
-- 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
Για να θεωρούνται διπλότυπες οι γραμμές μόνο όταν ταιριάζουν σε πολλές στήλες ταυτόχρονα, γράψε όλες αυτές τις στήλες και στο SELECT και στο GROUP BY.
Κανόνες που πρέπει να ξέρεις
-
Κάθε στήλη που επιλέγεις πρέπει να είναι στο
GROUP BYή μέσα σε συνάρτηση συνάθροισης. Αν προσθέσεις τοnameστο παραπάνω query, δεν θα δεις τα ονόματα των διπλότυπων. Θα πάρεις σφάλμα, εκτός από την SQLite, που δείχνει ένα όνομα ανά 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
Για να δεις κάθε διπλότυπη γραμμή, χρησιμοποίησε ένα από τα queries στην ενότητα «Εναλλακτική σύνταξη».
-
Τα NULL ομαδοποιούνται μαζί και τα πεζά-κεφαλαία μετράνε. Όλα τα NULL emails καταλήγουν σε μία ομάδα, οπότε μοιάζουν διπλότυπα μεταξύ τους, ενώ τα
Ana@Silva.devκαιana@silva.devόχι:
-- 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
Εναλλακτική σύνταξη
Το GROUP BY επιστρέφει μία γραμμή ανά διπλότυπη τιμή. Για να πάρεις τις ίδιες τις διπλότυπες γραμμές, μέτρα πάνω σε ένα window. Το COUNT(*) OVER (PARTITION BY email) βάζει το πλήθος κάθε email σε κάθε γραμμή που το έχει:
-- 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 κάνει το ίδιο χωρίς window function: κρατάει κάθε γραμμή για την οποία υπάρχει άλλη γραμμή με το ίδιο 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
Πότε πρέπει να ψάχνεις για διπλότυπα;
Τα διπλότυπα μπαίνουν από imports, διπλά κλικ, επαναλήψεις και constraints που λείπουν. Αυτές είναι οι τέσσερις δουλειές που θα χρειαστεί να κάνεις πιο συχνά γι' αυτά.
Έλεγξε ότι μια στήλη είναι μοναδική
Πριν προσθέσεις ένα constraint UNIQUE, ή όταν ένα login ή μια αναζήτηση αρχίζει να επιστρέφει δύο γραμμές, βρες τις τιμές που εμφανίζονται περισσότερες από μία φορές. Το GROUP BY με HAVING απαντά άμεσα σε αυτό.
Δες όλες τις γραμμές που εμπλέκονται
Δεν αρκεί να ξέρεις ποια τιμή επαναλαμβάνεται για να το διορθώσεις: χρειάζεσαι ολόκληρες τις γραμμές για να δεις πώς διαφέρουν και ποια είναι η σωστή. Τροφοδότησε τις διπλότυπες τιμές πίσω σε ένα query στον ίδιο πίνακα.
Σημείωσε τα επιπλέον αντίγραφα
Για να καθαρίσεις, κράτα μία γραμμή από κάθε σύνολο διπλότυπων και αφαίρεσε τις υπόλοιπες. Το ROW_NUMBER() αριθμεί τις γραμμές μέσα σε κάθε σύνολο, οπότε ό,τι είναι μετά τη γραμμή 1 είναι επιπλέον αντίγραφο.
Εντόπισε διπλές υποβολές
Ένα κουμπί που πατήθηκε δύο φορές ή μια πληρωμή που επαναλήφθηκε αφήνει δύο γραμμές που ταιριάζουν σε όλα εκτός από το ID τους. Αν ενώσεις τον πίνακα με τον εαυτό του, τις βρίσκεις σε ζεύγη, και το t1.id < t2.id εμφανίζει κάθε ζεύγος μία φορά.
Πραγματικά παραδείγματα εύρεσης διπλότυπων
Βρες δωμάτια ξενοδοχείου με διπλή κράτηση
Ένα δωμάτιο μπορεί να κλειστεί μόνο μία φορά ανά νύχτα, οπότε διπλότυπο είναι ο συνδυασμός ίδιου δωματίου και ίδιας νύχτας. Ομαδοποίησε και με τις δύο στήλες:
-- 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
Βρες λογαριασμούς που μοιράζονται διεύθυνση email
Κάποιοι εγγράφονται δύο φορές με την ίδια διεύθυνση γραμμένη με διαφορετικά πεζά-κεφαλαία. Κάνε partition με LOWER(email) για να δεις τους δύο λογαριασμούς δίπλα-δίπλα:
-- 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
Σημείωσε διπλότυπα άτομα πριν από καθαρισμό
Θεώρησε τους χρήστες με ίδιο όνομα και επώνυμο ως ένα άτομο, κράτα την παλαιότερη εγγραφή και σημείωσε τις υπόλοιπες. Ένα CTE κρατάει την αρίθμηση ξεχωριστά από τη σήμανση:
-- 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
Οι γραμμές με σήμανση Duplicate είναι αυτές που πρέπει να συγχωνεύσεις ή να διαγράψεις. Ο οδηγός αφαίρεσης διπλότυπων δείχνει πώς να τις διαγράψεις.
Βρες παραγγελίες που καταχωρήθηκαν δύο φορές κατά λάθος
Δύο παραγγελίες από τον ίδιο πελάτη για το ίδιο προϊόν την ίδια μέρα είναι πιθανότατα μία παραγγελία που υποβλήθηκε δύο φορές:
-- 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
Περισσότερα για τα διπλότυπα
Σε αυτό το blog:
- Οδηγός αφαίρεσης διπλότυπων στην SQL: διέγραψε τα επιπλέον αντίγραφα αφού τα βρεις
- Εντοπισμός διπλότυπων γραμμών σε πίνακα SQL Server: το ίδιο πρόβλημα σε T-SQL
- Οδηγός SQL COUNT DISTINCT: μέτρα τιμές χωρίς τα διπλότυπα
-
Οδηγός SQL GROUP BY: ομαδοποίηση και
HAVINGσε βάθος -
Having vs Where στην SQL: γιατί το φίλτρο πλήθους μπαίνει στο
HAVING - Δημιουργία unique constraints στην PostgreSQL: σταμάτα την είσοδο νέων διπλότυπων
- Οδηγός SQL CTE vs subquery: οι δύο τρόποι να δομήσεις αυτά τα queries
Επίσημη τεκμηρίωση:
- PostgreSQL: GROUP BY και HAVING, window functions
- MySQL: SELECT, μαζί με GROUP BY και HAVING, ROW_NUMBER()
- SQLite: SELECT, μαζί με GROUP BY και HAVING, window functions
- SQL Server: HAVING, ROW_NUMBER
Το Beekeeper Studio είναι ένα δωρεάν & open source GUI για βάσεις δεδομένων
Το καλύτερο εργαλείο για SQL queries & επεξεργασία που έχω χρησιμοποιήσει ποτέ. Μου δίνει ό,τι χρειάζομαι για να διαχειρίζομαι τη βάση δεδομένων μου. - ⭐⭐⭐⭐⭐ Mit
Το Beekeeper Studio είναι γρήγορο, εύχρηστο και κατανοητό με την πρώτη ματιά. Υποστηρίζει πάρα πολλές βάσεις δεδομένων και δουλεύει άψογα σε Windows, Mac και Linux.
Τι λένε οι χρήστες για το Beekeeper Studio
«Το Beekeeper Studio αντικατέστησε εντελώς την παλιά μου ροή εργασίας με SQL. Είναι γρήγορο, εύχρηστο και κάνει τη δουλειά με βάσεις δεδομένων ξανά απολαυστική.»
«Έχω δοκιμάσει πολλά GUI για βάσεις δεδομένων, αλλά το Beekeeper πετυχαίνει την τέλεια ισορροπία ανάμεσα σε λειτουργίες και απλότητα. Απλώς δουλεύει.»