Two identical cards with one set aside, representing finding and deleting duplicate records in SQL Server

SQL Server’da Yinelenen (Kopya) Kayıtları Bulmak ve Güvenle Silmek

SQL Server’da yinelenen kayıtları bulmak, ROW_NUMBER ile doğru satırı tutmak, bağlı kayıtları taşımak, büyük tablolarda parça parça silmek ve unique index ile tekrarı önlemek.

Mart 24, 2021

Kopya kayıtlar genellikle bir hatanın izidir: iki kez çalışan bir entegrasyon, unique kısıtlaması olmayan bir tablo, birden fazla kaynaktan birleştirilen veriler. Sonuç; şişen raporlar, iki kez gönderilen e-postalar ve yanlış toplamlardır. Kopyaları silmek tek bir sorguyla yapılabilir; ama doğru satırı tutmak, büyük tablolarda kilitlenmeden silmek ve sorunun tekrarlamasını önlemek biraz daha dikkat ister.

Bu rehberde kopyaları bulmayı, hangisinin tutulacağını belirleyerek güvenle silmeyi ve tekrarı önlemeyi anlatıyoruz.

SQL Server Danışmanlığı

Verilerinizde kopyalar mı birikiyor?

Aryasoft, kopyaların kaynağını bulur, verileri güvenle temizler ve tekrarlamaması için kısıtlamaları ve entegrasyonları düzeltir.

Destek Alın

Önce “Kopya” Tanımını Yapın

İki satırın kopya sayılması için hangi kolonların aynı olması gerektiğine iş birimiyle birlikte karar verin. Çoğu zaman tüm kolonlar değil, iş anahtarı (örneğin vergi numarası ya da sipariş numarası ve satır numarası) belirleyicidir. Tanım netleşmeden yapılan bir silme, gerçek kayıtları da götürebilir.

Kopyaları Bulmak

-- Aynı vergi numarasına sahip müşteriler
SELECT VergiNo, COUNT(*) AS adet
FROM dbo.Musteri
GROUP BY VergiNo
HAVING COUNT(*) > 1
ORDER BY adet DESC;

-- Kopya satırların tamamını görmek
SELECT m.*
FROM dbo.Musteri AS m
JOIN (SELECT VergiNo FROM dbo.Musteri GROUP BY VergiNo HAVING COUNT(*) > 1) AS d
     ON d.VergiNo = m.VergiNo
ORDER BY m.VergiNo, m.OlusturmaTarihi;

GROUP BY, NULL değerleri tek bir grup olarak kabul eder; iş anahtarında NULL olabilecek kolonlar varsa bunu ayrıca değerlendirin.

Hangi Satır Tutulacak?

ROW_NUMBER(), her kopya grubunda satırlara bir sıra numarası verir. ORDER BY ile hangi satırın 1 numara (tutulacak satır) olacağını siz belirlersiniz: en eski kayıt, en son güncellenen ya da en dolu olanı.

-- Önce kontrol: silinecek satırları görün (her grupta en eski kayıt kalır)
WITH sirali AS (
    SELECT MusteriId, VergiNo, OlusturmaTarihi,
           ROW_NUMBER() OVER (PARTITION BY VergiNo
                              ORDER BY OlusturmaTarihi ASC, MusteriId ASC) AS sira
    FROM dbo.Musteri
    WHERE VergiNo IS NOT NULL
)
SELECT * FROM sirali WHERE sira > 1;

ORDER BY içinde benzersiz bir kolon (örneğin MusteriId) bulunması, aynı tarihe sahip iki satır arasında hangisinin tutulacağını belirleyici hale getirir; aksi halde sonuç çalıştırmadan çalıştırmaya değişebilir.

Uyarı

WHERE VergiNo IS NOT NULL filtresini atlamayın. PARTITION BY, tıpkı GROUP BY gibi tüm NULL’ları tek bir grupta toplar; filtre olmazsa vergi numarası boş olan müşterilerin biri hariç hepsi, aslında farklı müşteriler oldukları halde kopya sayılır ve silinir.

Kritik

Kopya silmeden önce, silinecek satırları bir yedek tabloya kopyalayın (SELECT … INTO dbo.Musteri_Kopya_Yedek) ya da işlemden hemen önce bir log yedeği alın. Kopya kayıtlara bağlı sipariş, fatura gibi alt kayıtlar varsa, silmeden önce bu ilişkileri tutulacak satıra taşımanız gerekir; aksi halde foreign key hatası alırsınız ya da CASCADE kuralı varsa alt kayıtlar da silinir.

Bağlı Kayıtları Tutulan Satıra Taşımak

-- Kopya müşterilere bağlı siparişleri, tutulacak müşteriye bağla
WITH sirali AS (
    SELECT MusteriId,
           FIRST_VALUE(MusteriId) OVER (PARTITION BY VergiNo
                                        ORDER BY OlusturmaTarihi ASC, MusteriId ASC) AS tutulan_id,
           ROW_NUMBER() OVER (PARTITION BY VergiNo
                              ORDER BY OlusturmaTarihi ASC, MusteriId ASC) AS sira
    FROM dbo.Musteri
    WHERE VergiNo IS NOT NULL
)
UPDATE s
SET s.MusteriId = r.tutulan_id
FROM dbo.Siparis AS s
JOIN sirali AS r ON r.MusteriId = s.MusteriId
WHERE r.sira > 1;

Bunu müşteriye bağlı her tablo için tekrarlayın. Bir tabloya hangi tabloların bağlı olduğunu görmek için foreign key ilişkileri rehberimize bakabilirsiniz.

Kopyaları Silmek

Kopyalara bağlı alt kayıt kalmadığında, aynı sıralamayla kopyaları silin:

WITH sirali AS (
    SELECT ROW_NUMBER() OVER (PARTITION BY VergiNo
                              ORDER BY OlusturmaTarihi ASC, MusteriId ASC) AS sira
    FROM dbo.Musteri
    WHERE VergiNo IS NOT NULL
)
DELETE FROM sirali WHERE sira > 1;

Büyük Tablolarda Parça Parça Silmek

Milyonlarca kopyayı tek bir DELETE ile silmek; log’u şişirir, uzun süre kilit tutar ve Always On secondary’lerini geride bırakır. Silmeyi küçük parçalara bölün:

DECLARE @silinen int = 1;
WHILE @silinen > 0
BEGIN
    WITH sirali AS (
        SELECT ROW_NUMBER() OVER (PARTITION BY VergiNo
                                  ORDER BY OlusturmaTarihi ASC, MusteriId ASC) AS sira
        FROM dbo.Musteri
        WHERE VergiNo IS NOT NULL
    )
    DELETE TOP (5000) FROM sirali WHERE sira > 1;

    SET @silinen = @@ROWCOUNT;
END

Her parça ayrı bir transaction’dır; kilitler parçalar arasında bırakılır ve log yedekleri log alanını yeniden kullanılabilir hale getirir. İş anahtarı kolonunda bir index bulunması, her turda tablonun baştan taranmasını önler.

Tekrarlamasını Önlemek

Temizlik, kaynağı kapatılmazsa yalnızca geçici bir çözümdür:

-- İş anahtarında benzersizliği veritabanı düzeyinde zorunlu kıl
CREATE UNIQUE NONCLUSTERED INDEX UX_Musteri_VergiNo
ON dbo.Musteri (VergiNo)
WHERE VergiNo IS NOT NULL;   -- NULL'lara izin vermek gerekiyorsa filtered unique index

İpucu

Unique index’i oluşturduktan sonra, kopya üretmeye çalışan uygulama ya da entegrasyon hata almaya başlar. Bu iyi bir şeydir: sorunun kaynağını gösterir. Entegrasyonlarda “varsa güncelle, yoksa ekle” mantığını (UPDATE ardından INSERT ya da dikkatle kullanılan MERGE) uygulamak, kopyaların oluşmasını kalıcı olarak önler.

Kontrol Listesi

  • Kopya tanımı (iş anahtarı) iş birimiyle birlikte netleştirildi.
  • Tutulacak satırın kuralı belirlendi ve ORDER BY benzersiz bir kolonla bitiyor.
  • İş anahtarı NULL olan satırlar sıralamanın dışında tutuldu.
  • Silinecek satırlar önce SELECT ile kontrol edildi ve yedeklendi.
  • Bağlı kayıtlar tutulan satıra taşındı.
  • Büyük tablolarda silme parça parça yapıldı.
  • Unique index ile tekrar önlendi ve kopya üreten kaynak düzeltildi.
İş anahtarı kolonlarına göre GROUP BY ve HAVING COUNT(*) > 1 kullanarak kopya grupları bulunur.
ROW_NUMBER() OVER (PARTITION BY iş_anahtarı ORDER BY …) ile her gruba sıra numarası verilir ve sıra numarası 1’den büyük olan satırlar bir CTE üzerinden silinir.
DELETE TOP (n) ile küçük parçalar halinde, bir döngü içinde. Böylece log ve kilitler kontrol altında kalır.
İş anahtarı üzerinde unique index ya da unique constraint tanımlayarak ve kopya üreten uygulama veya entegrasyon mantığını düzelterek.

SQL Server Danışmanlığı

Verilerinizi temizleyin, kaynağını kapatın

Aryasoft’un kıdemli DBA ekibi kopya kayıtların kaynağını bulur, bağlı kayıtları koruyarak verileri güvenle temizler ve kısıtlamalarla tekrarını önler.

4.7/5