Cara Menghapus Data Duplikat di SQL: 3 Teknik Andalan
Blog/Tutorial SQL/Cara Menghapus Data Duplikat di SQL: 3 Teknik Andalan

Cara Menghapus Data Duplikat di SQL: 3 Teknik Andalan

BimaBima
·29 Mei 2026·11 menit baca

Penulis

Bima

Bima

Founder & Data Professional

Bagikan

TL;DR

Cara menghapus data duplikat di SQL ada tiga: DISTINCT buat baris yang identik seluruh kolomnya, GROUP BY buat nemuin dan ngitung duplikatnya, dan ROW_NUMBER() OVER (PARTITION BY ...) buat nyimpen satu baris terbaik dari tiap grup duplikat. ROW_NUMBER paling fleksibel karena kamu bisa nentuin baris mana yang dipertahankan. Selalu jalanin SELECT dulu sebelum DELETE, dan bikin backup tabel.

Cara menghapus data duplikat di SQL ada tiga jalur: DISTINCT buat baris yang identik semua kolomnya, GROUP BY buat nyelidiki duplikatnya di mana, dan ROW_NUMBER() buat nyimpen satu baris terbaik dari tiap grup.

Yang ketiga paling sering kepake di kerjaan nyata. Soalnya di data beneran, duplikat jarang identik 100% — biasanya beda di id, beda di timestamp, tapi kejadiannya sama.

Efeknya lumayan brutal. Duplikat 8% di tabel transaksi bikin laporan omzet kamu ngaku 8% lebih gede dari kenyataan. Dan gak ada error message buat itu.

Gimana cara ngecek ada duplikat atau nggak?

Sebelum hapus apa-apa, cek dulu. Ini query pertama yang selalu aku jalanin.

SELECT no_invoice, COUNT(*) AS jumlah_baris
FROM transaksi
GROUP BY no_invoice
HAVING COUNT(*) > 1
ORDER BY jumlah_baris DESC;

Logikanya sederhana. GROUP BY ngelompokkin baris berdasarkan nomor invoice. HAVING COUNT(*) > 1 nyaring cuma grup yang isinya lebih dari satu baris.

Kalau hasilnya kosong, selamat — gak ada duplikat berdasarkan invoice. Kalau keluar 200 baris, artinya ada 200 invoice yang kecatat lebih dari sekali.

Mau tau total baris yang kelebihan? Bungkus lagi.

SELECT
  COUNT(*) AS invoice_duplikat,
  SUM(jumlah_baris) - COUNT(*) AS baris_kelebihan
FROM (
  SELECT no_invoice, COUNT(*) AS jumlah_baris
  FROM transaksi
  GROUP BY no_invoice
  HAVING COUNT(*) > 1
) AS dup;

Angka baris_kelebihan ini yang bakal kamu hapus nanti. Catat, biar bisa dicocokin setelah DELETE.

Kalau HAVING masih asing, cek dulu panduan HAVING di ngulikdata.

Teknik 1: DISTINCT — buat duplikat yang identik total

DISTINCT ngebuang baris yang isinya persis sama di semua kolom yang kamu SELECT.

SELECT DISTINCT nama_pelanggan, kota, produk, qty, harga
FROM transaksi;

Kapan ini cocok? Kalau kamu lagi baca data mentah dari CSV yang keimport dua kali, dan tabelnya belum punya kolom id.

Jebakannya: begitu kamu SELECT * dan tabelnya punya id auto-increment, DISTINCT jadi gak berguna. Setiap baris punya id beda, jadi semuanya dianggap unik.

-- Ini gak ngapa-ngapain kalau ada kolom id
SELECT DISTINCT * FROM transaksi;

Jebakan kedua: spasi di ujung teks. 'Jakarta' dan 'Jakarta ' itu dua nilai berbeda buat SQL. Bersihin dulu.

SELECT DISTINCT
  TRIM(LOWER(nama_pelanggan)) AS nama_bersih,
  TRIM(kota) AS kota_bersih,
  produk
FROM transaksi;

TRIM ngebuang spasi di awal dan akhir. LOWER nyeragamin huruf besar-kecil. Dua fungsi ini nolongin banyak kasus "duplikat yang gak kedetect".

Teknik 2: GROUP BY — buat nyelidiki dan ngerangkum

GROUP BY bukan buat hapus, tapi buat ngerti. Ini yang kamu pakai sebelum mutusin apa-apa.

SELECT
  no_invoice,
  COUNT(*) AS berapa_kali,
  MIN(created_at) AS pertama_masuk,
  MAX(created_at) AS terakhir_masuk,
  COUNT(DISTINCT total_bayar) AS variasi_nilai
FROM transaksi
GROUP BY no_invoice
HAVING COUNT(*) > 1;

Kolom variasi_nilai itu kunci. Kalau nilainya 1, artinya semua duplikat invoice itu punya total_bayar yang sama — duplikat murni, aman dihapus.

Kalau nilainya 2 atau lebih, hati-hati. Artinya invoice yang sama punya nilai bayar berbeda. Itu bukan duplikat — itu data yang di-update, dan kamu perlu mutusin versi mana yang bener.

GROUP BY juga bisa kamu pakai buat bikin tabel bersih langsung, kalau agregasinya jelas.

CREATE TABLE transaksi_bersih AS
SELECT
  no_invoice,
  MAX(nama_pelanggan) AS nama_pelanggan,
  MAX(kota) AS kota,
  MAX(total_bayar) AS total_bayar,
  MAX(created_at) AS created_at
FROM transaksi
GROUP BY no_invoice;

Cepat, tapi kasar. MAX di sini cuma "ambil salah satu", dan kalau kolomnya banyak, kamu harus nulisin MAX di tiap kolom. Buat tabel dengan 30 kolom, ini melelahkan. Makanya ada teknik ketiga.

Teknik 3: ROW_NUMBER — andalan buat kerjaan nyata

Ini yang paling sering aku pakai. ROW_NUMBER() ngasih nomor urut ke tiap baris di dalam grup, dan kamu yang nentuin urutannya.

WITH ranked AS (
  SELECT
    id,
    no_invoice,
    nama_pelanggan,
    total_bayar,
    updated_at,
    ROW_NUMBER() OVER (
      PARTITION BY no_invoice
      ORDER BY updated_at DESC, id DESC
    ) AS rn
  FROM transaksi
)
SELECT * FROM ranked WHERE rn = 1;

Bacanya gini. PARTITION BY no_invoice artinya "reset hitungan tiap ganti invoice". ORDER BY updated_at DESC artinya "baris paling baru dapat nomor 1".

Jadi rn = 1 selalu baris yang mau kamu simpan. Sisanya — rn > 1 — duplikat yang mau kamu buang.

Kelebihannya dibanding DISTINCT: kamu punya kontrol penuh soal baris mana yang bertahan. Mau simpan yang paling baru? DESC. Mau yang paling lama? ASC. Mau yang nilainya paling gede? ORDER BY total_bayar DESC.

id DESC di belakang itu tie-breaker — kalau ada dua baris dengan updated_at persis sama, id yang lebih besar menang. Tanpa tie-breaker, hasilnya bisa beda tiap kali query dijalanin.

Baru DELETE-nya

WITH ranked AS (
  SELECT
    id,
    ROW_NUMBER() OVER (
      PARTITION BY no_invoice
      ORDER BY updated_at DESC, id DESC
    ) AS rn
  FROM transaksi
)
DELETE FROM transaksi
WHERE id IN (SELECT id FROM ranked WHERE rn > 1);

Sintaks CTE + DELETE ini jalan di PostgreSQL, SQL Server, dan MySQL 8+. Detail per-dialek bisa kamu cek di dokumentasi resmi window function PostgreSQL.

Buat pendalaman window function-nya sendiri, ada halaman ROW_NUMBER dan glossary window function.

Contoh kasus: 12.400 transaksi toko_berkah

Ini kejadian di dataset toko_berkah — data penjualan gabungan 6 toko kelontong di Bekasi, periode Januari sampai Maret 2026. Total 12.400 baris transaksi.

Laporan awal bilang omzet kuartal itu Rp 1,84 miliar. Angka itu dipakai buat nentuin target kuartal berikutnya.

Aku jalanin query pengecekan di atas. Hasilnya:

  • 1.042 invoice muncul lebih dari sekali
  • 1.118 baris kelebihan — sekitar 9,0% dari total data
  • Semuanya punya variasi_nilai = 1, jadi duplikat murni

Biang keroknya: toko cabang Bekasi Timur ngirim file harian mereka dua kali di 14 hari yang berbeda, gara-gara scriptnya nge-retry pas koneksi putus.

Setelah dedup pakai ROW_NUMBER, omzet aslinya Rp 1,67 miliar. Selisihnya Rp 170 juta — duit yang gak pernah ada, tapi hampir jadi dasar target tim sales.

Yang bikin ngeri: 9% itu gak cukup gede buat bikin orang curiga. Kalau duplikatnya 50%, semua orang langsung sadar. Yang 9% ini lewat begitu aja.

Langkah aman sebelum DELETE

Urutan yang aku pakai tiap kali, tanpa kecuali.

  1. Backup dulu. CREATE TABLE transaksi_backup_20260529 AS SELECT * FROM transaksi; Satu baris, nyelametin karir.
  2. Hitung baris sebelum. SELECT COUNT(*) FROM transaksi; Catat angkanya.
  3. Jalanin sebagai SELECT. Ganti DELETE FROM jadi SELECT * FROM, lihat baris apa aja yang bakal kena. Baca beberapa barisnya beneran.
  4. Cek jumlahnya cocok sama baris_kelebihan yang kamu itung di awal.
  5. Bungkus di transaksi kalau database-mu mendukung. BEGIN; → DELETE → cek COUNT(*) → COMMIT; atau ROLLBACK;

Nomor 3 yang paling sering diskip orang. Dan nomor 3 yang paling sering nyelametin.

Kesalahan umum waktu dedup

1. Nganggep semua duplikat itu salah. Pelanggan yang sama bisa beli produk yang sama dua kali di hari yang sama. Itu sah. Yang salah cuma kalau satu kejadian kecatat dua kali — ditandai kolom yang harusnya unik, kayak nomor invoice.

2. Lupa tie-breaker di ORDER BY. Tanpa tie-breaker, ROW_NUMBER ngasih hasil yang bisa beda tiap dijalanin. Selalu tambahin id atau primary key di ujung.

3. Dedup pakai kolom yang salah. Kalau kamu PARTITION BY nama_pelanggan, semua transaksi pelanggan itu jadi "duplikat" dan kamu bakal ngehapus riwayat belanjanya. Pastiin kolom partisinya beneran identitas satu kejadian.

4. DELETE tanpa backup. Gak ada Ctrl+Z di production database.

5. Gak benerin sumbernya. Kamu bersihin hari ini, minggu depan duplikatnya balik lagi. Tambahin UNIQUE constraint di kolom invoice, atau perbaiki script import-nya.

ALTER TABLE transaksi
ADD CONSTRAINT uq_no_invoice UNIQUE (no_invoice);

FAQ

Apa cara paling aman menghapus duplikat di SQL?

Pakai ROW_NUMBER() OVER (PARTITION BY kolom_kunci ORDER BY kolom_prioritas), lalu hapus baris yang rn > 1. Sebelum DELETE, ganti dulu jadi SELECT dan cek berapa baris yang bakal kena. Bikin backup tabel, dan jalanin di dalam transaksi kalau database kamu mendukung, biar bisa ROLLBACK.

Apa bedanya DISTINCT dan GROUP BY buat hapus duplikat?

DISTINCT ngebuang baris yang isinya persis sama di semua kolom yang kamu SELECT. GROUP BY ngelompokkin baris berdasarkan kolom tertentu, jadi kamu bisa ngitung ada berapa duplikat pakai COUNT(*). Buat nampilin data bersih, DISTINCT lebih pendek. Buat nyelidiki duplikatnya seberapa parah, GROUP BY plus HAVING yang kamu butuhin.

Kenapa DISTINCT gak menghapus duplikat saya?

Biasanya gara-gara ada kolom yang nilainya beda tipis — id auto-increment, timestamp, atau spasi di ujung teks. DISTINCT ngebandingin seluruh baris, jadi satu karakter beda aja udah dianggap unik. Batasi kolom di SELECT cuma ke kolom yang nentuin keunikan, atau bersihin dulu pakai TRIM dan LOWER.

Gimana cara hapus duplikat tapi simpan yang paling baru?

Pakai ROW_NUMBER() dengan ORDER BY tanggal DESC di dalam PARTITION BY. Baris paling baru bakal dapat rn = 1, sisanya 2, 3, dan seterusnya. Habis itu hapus semua baris dengan rn > 1. Mau simpan yang paling lama? Ganti DESC jadi ASC.

Duplikat di data transaksi itu selalu salah?

Nggak selalu. Pelanggan yang sama bisa beli barang yang sama dua kali di hari yang sama, dan itu sah. Yang bikin duplikat jadi masalah adalah kalau satu kejadian nyata kecatat lebih dari sekali — biasanya gara-gara import ganda atau retry gagal. Cek dulu kolom yang harusnya unik, kayak nomor invoice.

Penutup

Ringkasnya. Pakai GROUP BY + HAVING buat nyelidiki dulu — selalu, sebelum ngapa-ngapain.

Pakai ROW_NUMBER() buat hapus, soalnya cuma teknik ini yang ngasih kamu kontrol soal baris mana yang bertahan.

Dan tambahin UNIQUE constraint setelah beres, biar kamu gak ngerjain ini lagi bulan depan.

Mau latihan query dedup pakai data beneran? Dataset toko_berkah ada di editor interaktif ngulikdata — mulai dari panduan belajar SQL dari nol kalau kamu baru mulai.

Coba Langsung

Mau praktek langsung? Mulai latihan SQL gratis

Latihan interaktif, langsung di browser.

Buka NgulikSQL →
Bagikan:
Bima
Ditulis oleh

Bima

Founder & Data Professional

Founder Ngulik Data. Passionate about making data analysis accessible for everyone.

Artikel Terkait

7 SQL Client Gratis Terbaik buat Analis Data (2026)
Tutorial SQL
10 Juli 2026•9 menit baca

7 SQL Client Gratis Terbaik buat Analis Data (2026)

Nggak perlu bayar buat nulis query. Ini 7 SQL client gratis yang beneran dipakai analis, plus kelebihan dan kekurangannya masing-masing.

BimaBima
SQL Style Guide: Cara Nulis Query yang Enak Dibaca Tim
Tutorial SQL
7 Juli 2026•9 menit baca

SQL Style Guide: Cara Nulis Query yang Enak Dibaca Tim

Sepuluh aturan SQL style guide yang bikin query kamu kebaca sama tim — dan sama kamu sendiri 6 bulan lagi. Lengkap dengan contoh sebelum-sesudah.

BimaBima
Take-Home Test SQL Data Analyst: Contoh Soal + Cara Ngerjainnya
Tutorial SQL
4 Juli 2026•11 menit baca

Take-Home Test SQL Data Analyst: Contoh Soal + Cara Ngerjainnya

Contoh soal take-home test SQL yang beneran dipakai perusahaan, dikerjain step by step — dari baca soal, nulis query, sampai nyusun insight yang bikin recruiter nengok.

BimaBima
Kembali ke Blog
Ngulik Data logoNgulik Data

Platform edukasi data lengkap untuk professionals Indonesia. Belajar SQL, Data Analysis, dan lebih banyak lagi dengan praktek langsung dan feedback real-time.

Copyright © 2026 - All rights reserved

LINKS
SupportPricingDatasetBlogAffiliates
LEGAL
Terms of servicesPrivacy policy
Ngulik Data
DatasetLeaderboardBlogStore