Hapus Duplikat dan Simpan Baris Terbaru di SQL
TL;DR
Buat hapus duplikat di SQL dan simpan baris terbaru, kasih nomor urut tiap baris pakai ROW_NUMBER() OVER (PARTITION BY kolom_kunci ORDER BY tanggal DESC), lalu hapus semua baris yang nomornya lebih dari 1. Cara ini nyimpen versi paling baru per grup dan buang sisanya dalam satu query.
Buat hapus duplikat di SQL tapi tetap simpan baris paling baru, kasih nomor urut tiap baris dengan ROW_NUMBER(), urutkan dari yang terbaru, lalu hapus baris yang nomornya lebih dari 1.
Duplikat itu masalah yang sering ketemu waktu data masuk dari banyak sumber. Kasir sinkron dua kali, form kekirim ganda, atau proses import diulang. Hasilnya satu transaksi kecatat berkali-kali.
Di artikel ini kamu bakal tau cara nemuin duplikat, milih baris mana yang mau disimpen, dan hapus sisanya tanpa ngerusak data lain.
Kenapa data duplikat bisa muncul di database?
Duplikat muncul waktu satu kejadian nyata kecatat jadi beberapa baris. Penyebab paling umum: proses import dijalankan dua kali, integrasi API kirim data ganda, atau nggak ada aturan unik di tabelnya. Selama nggak ada PRIMARY KEY atau constraint unik, database nerima semua baris apa adanya.
Yang bikin repot, kadang baris duplikat nggak 100% sama. Baris kedua bisa punya updated_at lebih baru atau status yang berubah. Jadi kamu nggak bisa asal buang, kamu harus milih versi mana yang paling benar.
Sebelum hapus apa pun, cek dulu ada berapa duplikatnya. Kalau kamu belum kenal dasarnya, baca dulu panduan data cleaning SQL biar paham urutan bersihin data.
Gimana cara cek ada berapa baris duplikat?
Buat cek duplikat, pakai GROUP BY pada kolom yang harusnya unik, lalu saring grup yang jumlahnya lebih dari satu pakai HAVING. Query ini kasih tau kombinasi mana yang kecatat lebih dari sekali dan berapa kali munculnya.
-- Cek kombinasi yang muncul lebih dari sekali
SELECT order_id, produk, COUNT(*) AS jumlah
FROM transaksi
GROUP BY order_id, produk
HAVING COUNT(*) > 1
ORDER BY jumlah DESC;Hasilnya tabel berisi kombinasi yang double plus hitungannya. Ini penting biar kamu tau skala masalahnya sebelum mulai hapus.
Gimana cara hapus duplikat dan simpan baris terbaru di SQL?
Cara paling aman: kasih nomor urut per grup pakai ROW_NUMBER(), urutkan dari data terbaru, terus hapus baris yang nomornya bukan 1. Baris nomor 1 selalu versi paling baru, sisanya duplikat lama yang bisa dibuang.
Fungsi ROW_NUMBER() ini bagian dari window function, cara ngasih nomor baris tanpa nge-grup datanya. Kalau mau ngerti lebih dalam, cek tutorial window function ROW_NUMBER dan RANK dan definisi window function.
Langkahnya gini:
- Bikin nomor urut per grup, urut dari
updated_atpaling baru. - Bungkus pakai CTE biar hasilnya gampang dipanggil.
- Hapus semua baris yang nomor urutnya lebih dari 1.
-- Hapus duplikat, simpan baris paling baru per order_id
WITH ranked AS (
SELECT
ctid, -- PostgreSQL: alamat fisik baris
ROW_NUMBER() OVER (
PARTITION BY order_id
ORDER BY updated_at DESC
) AS rn
FROM transaksi
)
DELETE FROM transaksi
WHERE ctid IN (
SELECT ctid FROM ranked WHERE rn > 1
);Di PostgreSQL, ctid nunjuk ke baris fisik yang unik, jadi aman buat ngehapus. Di MySQL, ganti pakai kolom id auto increment sebagai penanda barisnya.
Gimana caranya kalau di MySQL tanpa ctid?
Di MySQL kamu pakai kolom id sebagai penanda unik, lalu hapus baris yang id-nya masuk daftar duplikat. Logikanya sama: nomorin per grup pakai ROW_NUMBER() (MySQL 8+), simpan yang nomor 1, buang sisanya.
-- MySQL 8+: hapus duplikat, simpan yang terbaru
WITH ranked AS (
SELECT id,
ROW_NUMBER() OVER (
PARTITION BY order_id
ORDER BY updated_at DESC
) AS rn
FROM transaksi
)
DELETE FROM transaksi
WHERE id IN (SELECT id FROM ranked WHERE rn > 1);Kalau versi MySQL kamu di bawah 8, kamu belum punya ROW_NUMBER(). Solusinya, simpan id terbaru per grup pakai subquery, lalu hapus baris yang id-nya bukan itu.
-- MySQL lama: simpan id maksimum per grup
DELETE t FROM transaksi t
JOIN (
SELECT order_id, MAX(id) AS id_terbaru
FROM transaksi
GROUP BY order_id
) keep
ON t.order_id = keep.order_id
WHERE t.id <> keep.id_terbaru;Apa itu DISTINCT ON di PostgreSQL?
DISTINCT ON itu fitur khusus PostgreSQL buat ambil satu baris per grup tanpa nomorin manual. Kamu sebut kolom kuncinya, urutkan, dan PostgreSQL ambil baris pertama tiap grup. Cocok buat bikin tabel bersih baru, bukan buat DELETE langsung.
-- Bikin tabel bersih berisi 1 baris terbaru per order_id
CREATE TABLE transaksi_bersih AS
SELECT DISTINCT ON (order_id) *
FROM transaksi
ORDER BY order_id, updated_at DESC;Cara ini enak kalau kamu pengen simpan tabel asli utuh dan kerja di salinan bersihnya. Bandingin sama cara kerja DISTINCT biasa yang cuma buang baris yang identik penuh.
Contoh kasus: bersihin transaksi toko_berkah
Aku pernah bantu bersihin tabel transaksi punya toko_berkah, UMKM sembako dengan 3 cabang. Tabelnya punya 12.400 baris hasil gabungan export kasir harian.
Waktu dicek pakai GROUP BY order_id HAVING COUNT(*) > 1, ketemu 1.836 baris duplikat, sekitar 14,8% dari total. Penyebabnya kasir cabang Pasar Minggu sync dua kali tiap tutup toko.
Yang bikin bahaya: sebagian duplikat punya status beda. Baris lama statusnya pending, baris baru lunas. Kalau asal hapus tanpa urut updated_at DESC, bisa kebuang yang lunas dan revenue kecatat kurang.
Setelah pakai ROW_NUMBER() urut dari yang terbaru, angka total penjualan turun dari Rp 512 juta jadi Rp 448 juta. Selisih Rp 64 juta itu penjualan hantu dari baris ganda. Laporan bulanan yang tadinya kelihatan naik, ternyata flat.
Kesalahan umum waktu hapus duplikat
- Nggak backup dulu. DELETE nggak bisa di-undo. Selalu
CREATE TABLE backup AS SELECT * FROM transaksisebelum mulai. - Lupa ORDER BY di ROW_NUMBER. Tanpa
ORDER BY, urutan barisnya acak. Kamu bisa nyimpen versi lama tanpa sadar. - PARTITION BY kolom yang salah. Kalau kolom kuncinya kurang lengkap, baris yang beda bisa keanggep sama dan kehapus.
- Langsung DELETE tanpa cek jumlah. Jalanin
SELECTversi rank-nya dulu, lihat baris mana yang bakal kena, baru ganti jadi DELETE.
Tips: bungkus pakai CTE biar query gampang dibaca dan dites bertahap. Pelajari cara pakai CTE di SQL kalau belum familiar.
FAQ
Apa beda hapus duplikat pakai DISTINCT dan ROW_NUMBER?
DISTINCT cuma buang baris yang identik di semua kolom, dan dia nggak bisa milih versi mana yang disimpen. ROW_NUMBER kasih kamu kontrol penuh: kamu tentuin kolom kunci lewat PARTITION BY dan tentuin baris mana yang menang lewat ORDER BY. Kalau duplikatnya beda di kolom tanggal atau status, kamu wajib pakai ROW_NUMBER biar bisa simpan yang paling baru.
Gimana cara simpan baris terlama, bukan terbaru?
Tinggal ganti arah urutannya. Pakai ORDER BY updated_at ASC di dalam ROW_NUMBER, jadi baris nomor 1 adalah yang paling lama. Sisanya yang lebih baru bakal kehapus. Logika sisa querynya sama persis, cuma arah ASC dan DESC yang beda.
Apakah aman jalanin DELETE ini di database produksi?
Aman kalau kamu backup dulu dan tes pakai SELECT sebelum DELETE. Jalanin versi SELECT dari CTE-nya, hitung ada berapa baris yang rn lebih dari 1, dan pastikan angkanya masuk akal. Kalau bisa, jalanin di jam sepi dan bungkus dalam transaksi biar bisa ROLLBACK kalau hasilnya nggak sesuai.
Kenapa pakai ctid di PostgreSQL, bukan kolom id?
Kalau tabelmu nggak punya kolom id atau primary key, kamu butuh penanda unik lain buat nunjuk baris tertentu. ctid itu alamat fisik baris di PostgreSQL, selalu unik per baris. Tapi ctid bisa berubah setelah VACUUM, jadi pakai dia cuma dalam satu query, jangan disimpan buat dipakai nanti.
Gimana cara cegah duplikat masuk lagi ke depannya?
Tambahin constraint unik di kolom kunci pakai ALTER TABLE transaksi ADD CONSTRAINT uniq_order UNIQUE (order_id). Setelah itu database bakal nolak baris yang order_id-nya udah ada. Buat data dari import, pakai INSERT ... ON CONFLICT DO UPDATE di PostgreSQL biar baris lama ke-update, bukan digandakan.
Penutup
Tiga hal yang perlu kamu inget: cek dulu jumlah duplikat pakai GROUP BY dan HAVING, pakai ROW_NUMBER dengan ORDER BY buat milih baris terbaru, dan selalu backup sebelum DELETE.
Sekarang giliran kamu coba. Latihan langsung pakai dataset transaksi Indonesia di NgulikSQL, editornya jalan di browser tanpa perlu install database.
Mau lanjut? Baca teknik data cleaning SQL lainnya atau cek dokumentasi resmi window function PostgreSQL buat pendalaman ROW_NUMBER.
Mau praktek langsung? Mulai latihan SQL gratis
Latihan interaktif, langsung di browser.
Artikel terkait
Composite Index di SQL: Cara Kerja dan Urutan Kolom
Composite index itu index atas beberapa kolom sekaligus. Urutan kolomnya nentuin query mana yang kebantu, lewat aturan leftmost prefix.
Menghitung Stickiness (DAU/MAU) di SQL
Stickiness ngukur seberapa sering user balik lagi. Ini cara hitung DAU, MAU, dan rasio stickiness pakai SQL, plus contoh aplikasi UMKM.
Menghitung DAU dan MAU dengan SQL
DAU dan MAU dua metrik wajib buat produk digital. Ini cara hitungnya pakai SQL, lengkap query DISTINCT, per bulan, sampai rasio stickiness.