MySQL ve MariaDB’de DISTINCT ile Tekrar Eden Kayıtları Kaldırma
Veritabanı yönetiminde en sık karşılaşılan sorunların başında tekrar eden kayıtlar gelir. Bir e-ticaret sitesinde aynı müşterinin iki kez listelendiğini, bir log tablosunda aynı olayın onlarca kez yazıldığını ya da bir raporlama sorgusunda aynı satırların defalarca göründüğünü fark ettiğinizde, ilk içgüdünüz muhtemelen “bu verileri nasıl temizlerim?” olmaktadır. İşte tam bu noktada SQL’in DISTINCT anahtar kelimesi devreye girer. Bu yazıda MariaDB ve MySQL özelinde DISTINCT kullanımını, gerçek dünya senaryolarıyla birlikte derinlemesine inceleyeceğiz.
DISTINCT Nedir ve Ne Zaman Kullanılır?
DISTINCT, bir SELECT sorgusunun sonuç kümesindeki tekrar eden satırları eleyen bir SQL anahtar kelimesidir. Temel mantığı son derece basittir: aynı değerlere sahip birden fazla satır varsa, bunların yalnızca bir tanesini döndür.
Peki bu ne zaman gerçekten gereklidir? Birkaç tipik senaryo düşünelim:
- Bir müşteri tablosunda aynı şehirden kaç farklı şehir olduğunu listelemek istiyorsunuz
- Sipariş geçmişinde hangi ürünlerin satıldığını, kaç kez değil sadece hangileri olduğunu görmek istiyorsunuz
- JOIN işlemleri sonrası ortaya çıkan tekrarlı satırları temizlemek istiyorsunuz
- Bir log tablosundan benzersiz IP adreslerini çekmek istiyorsunuz
DISTINCT olmadan bu sorguları yazdığınızda sonuçlar şişer, raporlar yanıltıcı olur ve gereksiz yere performans kaybı yaşanır.
Temel Söz Dizimi
Başlangıç olarak en basit kullanım biçimine bakalım. Söz dizimi oldukça sezgiseldir:
SELECT DISTINCT kolon_adi
FROM tablo_adi;
Gerçek bir örnek üzerinden gidelim. Diyelim ki musteriler adında bir tablomuz var ve sehir kolonunda çok sayıda tekrar eden değer var:
-- Tekrarlı sonuç döndüren sorgu
SELECT sehir FROM musteriler;
-- DISTINCT ile benzersiz şehirleri getiren sorgu
SELECT DISTINCT sehir FROM musteriler;
İlk sorgu 10.000 satır döndürebilirken, ikincisi yalnızca 81 satır döndürebilir. Bu fark, hem okunabilirlik hem de performans açısından büyük önem taşır.
Çoklu Kolon ile DISTINCT Kullanımı
DISTINCT yalnızca tek kolonla sınırlı değildir. Birden fazla kolonu birlikte kullandığınızda, kombinasyonların benzersizliği değerlendirilir. Yani her kolondaki değer ayrı ayrı değil, birlikte ele alınır.
SELECT DISTINCT sehir, ilce
FROM musteriler
ORDER BY sehir, ilce;
Bu sorgu, sehir ve ilce kombinasyonlarının her birini yalnızca bir kez döndürür. İstanbul-Kadıköy çifti birden fazla müşteride geçse bile sonuç kümesinde yalnızca bir kez görünür.
Biraz daha karmaşık bir senaryo düşünelim. Bir e-ticaret platformunda siparişleri ve ürünleri takip eden bir yapınız olsun:
-- Hangi müşteri hangi kategori ürün aldı? (tekrarsız liste)
SELECT DISTINCT
m.musteri_id,
m.ad,
m.soyad,
u.kategori
FROM musteriler m
JOIN siparisler s ON m.musteri_id = s.musteri_id
JOIN urunler u ON s.urun_id = u.urun_id
ORDER BY m.soyad, u.kategori;
Bu sorgu, aynı müşterinin aynı kategoriden onlarca ürün almış olmasına rağmen her müşteri-kategori çiftini yalnızca bir kez listeler.
DISTINCT ve NULL Değerlerin Davranışı
Sysadmin olarak veritabanıyla çalışırken NULL değerlerin nasıl işlendiğini bilmek kritik önemdedir. DISTINCT açısından bakıldığında, NULL değerler birbirine eşit kabul edilir. Yani bir kolonda 50 tane NULL varsa, DISTINCT bunların hepsini tek bir NULL olarak işler.
-- telefon kolonunda NULL değerlerin durumunu test et
SELECT DISTINCT telefon
FROM musteriler
ORDER BY telefon;
Çıktıda NULL bir kez görünecektir, 50 kez değil. Bu davranış bazen beklenmedik sonuçlar doğurabilir. Özellikle raporlama sorgularında NULL değerleri ayrıca ele almak isteyebilirsiniz:
-- NULL olanları filtrele, sadece gerçek değerleri unique listele
SELECT DISTINCT telefon
FROM musteriler
WHERE telefon IS NOT NULL
ORDER BY telefon;
COUNT ile Birlikte DISTINCT Kullanımı
Sistem yöneticilerinin sıklıkla ihtiyaç duyduğu bir pattern, benzersiz kayıt sayısını hesaplamaktır. COUNT(DISTINCT kolon) söz dizimi tam da bu işe yarar:
-- Kaç farklı şehirden müşterimiz var?
SELECT COUNT(DISTINCT sehir) AS benzersiz_sehir_sayisi
FROM musteriler;
-- Kaç farklı IP'den sisteme giriş yapılmış?
SELECT
COUNT(DISTINCT ip_adresi) AS benzersiz_ip,
COUNT(*) AS toplam_giris,
DATE(giris_zamani) AS tarih
FROM sistem_log
GROUP BY DATE(giris_zamani)
ORDER BY tarih DESC;
İkinci sorgu özellikle güvenlik izleme açısından son derece değerlidir. Bir günde toplam 50.000 giriş denemesi ama yalnızca 3 farklı IP varsa, bu durum kaba kuvvet saldırısına işaret edebilir.
GROUP BY ile DISTINCT Karşılaştırması
Pek çok sysadmin ve geliştirici, DISTINCT yerine GROUP BY kullanır ya da tam tersini yapar. İkisi de tekrar eden kayıtları eleyebilir, ancak aralarında önemli farklar vardır.
-- DISTINCT kullanımı
SELECT DISTINCT sehir FROM musteriler ORDER BY sehir;
-- Eşdeğer GROUP BY kullanımı
SELECT sehir FROM musteriler GROUP BY sehir ORDER BY sehir;
Her iki sorgu da aynı sonucu verir. Ancak GROUP BYın asıl gücü agregasyon fonksiyonlarıyla birlikte kullanıldığında ortaya çıkar:
-- GROUP BY ile şehir bazında müşteri sayısı
SELECT
sehir,
COUNT(*) AS musteri_sayisi,
AVG(yas) AS ortalama_yas
FROM musteriler
GROUP BY sehir
ORDER BY musteri_sayisi DESC;
Bunu DISTINCT ile yapamazsınız çünkü DISTINCT yalnızca belirli kolonların benzersiz kombinasyonlarını döndürür, agregasyon hesaplamaz. Kural olarak şunu söyleyebiliriz: Sadece tekrarları elemek istiyorsanız DISTINCT, gruplama ve hesaplama yapacaksanız GROUP BY kullanın.
JOIN Sorgularında DISTINCT
JOIN kullanıldığında tekrarlı satırlar neredeyse kaçınılmazdır. Özellikle bire-çok ilişkilerde sol tablodaki bir kayıt, sağ tablodaki birden fazla kayıtla eşleşince sol taraftaki veriler tekrar eder. İşte bu durumda DISTINCT kurtarıcı olur:
-- Kategoriye göre filtreleme yapılmış ürün tablosu
-- Her üründen birden fazla sipariş olsa da ürünü tek listele
SELECT DISTINCT
u.urun_id,
u.urun_adi,
u.fiyat,
k.kategori_adi
FROM urunler u
INNER JOIN kategoriler k ON u.kategori_id = k.kategori_id
INNER JOIN siparis_detay sd ON u.urun_id = sd.urun_id
WHERE k.kategori_adi = 'Elektronik'
ORDER BY u.urun_adi;
Bu sorgu olmadan, aynı ürün 1000 sipariş sırasında alındıysa 1000 kez listelenirdi. DISTINCT bu problemi tek satırda çözer.
Daha gerçekçi bir senaryo: Bir hosting şirketinde çalıştığınızı düşünün. Sunucu bazlı servis listesi çıkarmak istiyorsunuz:
-- Hangi sunucularda hangi servisler çalışıyor? (Her çifti tek kez göster)
SELECT DISTINCT
s.sunucu_adi,
s.ip_adresi,
sv.servis_adi,
sv.port
FROM sunucular s
JOIN sunucu_servisler ss ON s.sunucu_id = ss.sunucu_id
JOIN servisler sv ON ss.servis_id = sv.servis_id
WHERE s.durum = 'aktif'
ORDER BY s.sunucu_adi, sv.servis_adi;
Alt Sorgularda DISTINCT Kullanımı
DISTINCT yalnızca ana sorguda değil, alt sorgularda (subquery) da kullanılabilir. Bu, karmaşık sorgularda tekrarlı ara sonuçların önüne geçmek için önemlidir:
-- Hiç sipariş vermemiş müşterileri bul
-- Alt sorgu unique siparis_id listesi döndürür
SELECT
m.musteri_id,
m.ad,
m.soyad,
m.email
FROM musteriler m
WHERE m.musteri_id NOT IN (
SELECT DISTINCT musteri_id
FROM siparisler
WHERE durum != 'iptal'
);
Başka bir senaryo, veri temizleme süreçlerinde sıklıkla karşınıza çıkar:
-- Son 6 ayda birden fazla kategoride alışveriş yapan müşteriler
SELECT
m.ad,
m.soyad,
m.email,
kategori_sayisi
FROM musteriler m
JOIN (
SELECT
musteri_id,
COUNT(DISTINCT kategori_id) AS kategori_sayisi
FROM siparisler s
JOIN urunler u ON s.urun_id = u.urun_id
WHERE s.siparis_tarihi >= DATE_SUB(NOW(), INTERVAL 6 MONTH)
GROUP BY musteri_id
HAVING COUNT(DISTINCT kategori_id) > 1
) AS aktif ON m.musteri_id = aktif.musteri_id
ORDER BY kategori_sayisi DESC;
Performans Konuları: DISTINCT’i Doğru Kullanmak
Bu noktada sysadmin şapkanızı takmanız gerekiyor. DISTINCT her ne kadar kullanışlı olsa da, yanlış veya gereksiz kullanıldığında ciddi performans sorunlarına yol açar.
DISTINCT, arka planda bir sıralama veya gruplama işlemi gerçekleştirir. Büyük tablolarda bu işlem hem CPU hem de bellek açısından maliyetlidir. Sorgu planını her zaman EXPLAIN ile kontrol edin:
-- Sorgu planını incele
EXPLAIN SELECT DISTINCT sehir, ilce
FROM musteriler
WHERE aktif = 1;
-- Daha detaylı analiz için
EXPLAIN FORMAT=JSON SELECT DISTINCT sehir, ilce
FROM musteriler
WHERE aktif = 1;
EXPLAIN çıktısında Using temporary ve Using filesort ifadelerini görüyorsanız, sorgu disk I/O kullanıyor demektir. Bu, büyük veri setlerinde fark edilebilir yavaşlamaya neden olur.
Performans iyileştirmeleri için şunlara dikkat edin:
- WHERE koşullarını ekleyin:
DISTINCTöncesinde satır sayısını azaltın - İndeks kullanımı:
DISTINCTyapılacak kolonlarda uygun indeksler tanımlayın - Gereksiz DISTINCT kullanmayın: Zaten PRIMARY KEY olan bir kolonda
DISTINCTkullanmak anlamsızdır - LIMIT ile birleştirin: Sonuç kümesini sınırlayarak bellek kullanımını azaltın
-- İndeks oluşturarak DISTINCT performansını artır
CREATE INDEX idx_musteri_sehir ON musteriler(sehir);
CREATE INDEX idx_musteri_sehir_ilce ON musteriler(sehir, ilce);
-- LIMIT ile birlikte DISTINCT
SELECT DISTINCT sehir
FROM musteriler
WHERE aktif = 1
ORDER BY sehir
LIMIT 20;
DISTINCT ile Veri Temizliği: Gerçek Dünya Senaryosu
Bir sabah iş başında, DBA’dan gelen bir mesaj: “Müşteri tablosunda binlerce tekrarlı kayıt var, ne yapalım?” Bu oldukça yaygın bir durumdur. DISTINCT ile bu sorunu nasıl ele alacağınızı görelim:
-- Önce tekrarlı kayıtları tespit et
SELECT
email,
COUNT(*) AS tekrar_sayisi
FROM musteriler
GROUP BY email
HAVING COUNT(*) > 1
ORDER BY tekrar_sayisi DESC;
-- Tekrarlı kayıtların detayını görmek için
SELECT *
FROM musteriler
WHERE email IN (
SELECT email
FROM musteriler
GROUP BY email
HAVING COUNT(*) > 1
)
ORDER BY email, olusturma_tarihi;
Sonuçları inceledikten sonra, benzersiz kayıtları yeni bir tabloya aktarabilirsiniz:
-- Temiz veriyi yeni tabloya aktar (en eski kaydı tut)
CREATE TABLE musteriler_temiz AS
SELECT *
FROM musteriler
WHERE musteri_id IN (
SELECT MIN(musteri_id)
FROM musteriler
GROUP BY email
);
-- Kayıt sayılarını karşılaştır
SELECT
(SELECT COUNT(*) FROM musteriler) AS orijinal_kayit,
(SELECT COUNT(*) FROM musteriler_temiz) AS temiz_kayit,
(SELECT COUNT(*) FROM musteriler) - (SELECT COUNT(*) FROM musteriler_temiz) AS silinen_kayit;
DISTINCT ON: PostgreSQL ile Fark
MariaDB ve MySQL kullanıcısı olarak belirtmek gerekir ki, PostgreSQL’de DISTINCT ON (kolon) söz dizimi bulunur ve bu çok daha güçlü bir özellik sunar. MariaDB/MySQL’de bu söz dizimi desteklenmez. Bununla birlikte benzer sonuçlara GROUP BY ve HAVING kombinasyonuyla ulaşabilirsiniz:
-- MariaDB/MySQL'de "DISTINCT ON" benzeri davranış
-- Her şehirdeki en yüksek gelirli müşteriyi getir
SELECT m.*
FROM musteriler m
INNER JOIN (
SELECT sehir, MAX(gelir) AS max_gelir
FROM musteriler
GROUP BY sehir
) AS en_yuksek ON m.sehir = en_yuksek.sehir
AND m.gelir = en_yuksek.max_gelir;
Log Analizi Senaryosu: Güvenlik İzleme
Bir sysadmin olarak en çok işinize yarayacak pratik kullanım case’lerinden biri log analizidir. Sistem güvenlik loglarında DISTINCT ile yapabileceğiniz analizler:
-- Son 24 saatte sisteme erişen benzersiz IP'ler
SELECT DISTINCT
ip_adresi,
kullanici_adi,
MIN(erisim_zamani) AS ilk_erisim,
MAX(erisim_zamani) AS son_erisim,
COUNT(*) AS toplam_istek
FROM erisim_loglari
WHERE erisim_zamani >= DATE_SUB(NOW(), INTERVAL 24 HOUR)
AND sonuc = 'BASARISIZ'
GROUP BY ip_adresi, kullanici_adi
HAVING COUNT(*) > 10
ORDER BY toplam_istek DESC;
Bu sorgu, son 24 saatte 10’dan fazla başarısız giriş denemesi yapan benzersiz IP-kullanıcı kombinasyonlarını listeler. Bir güvenlik duvarı kural güncellemesi için mükemmel bir temel oluşturur.
-- Hangi saatlerde benzersiz kullanıcı sayısı en yüksek?
SELECT
HOUR(erisim_zamani) AS saat,
COUNT(DISTINCT kullanici_adi) AS benzersiz_kullanici,
COUNT(*) AS toplam_istek
FROM erisim_loglari
WHERE erisim_zamani >= DATE_SUB(NOW(), INTERVAL 7 DAY)
GROUP BY HOUR(erisim_zamani)
ORDER BY saat;
Yaygın Hatalar ve Bunlardan Kaçınma
Yıllar içinde görülen yaygın DISTINCT hatalarını bilmek, hata yapmadan önce fark etmenizi sağlar:
- Tüm kolonlara DISTINCT uygulamak:
SELECT DISTINCT *genellikle işe yaramaz çünkü ID kolonları satırları zaten benzersiz kılar - DISTINCT ile sıralama çelişkisi:
ORDER BYkolonununSELECTlistesinde olduğundan emin olun - Gereksiz DISTINCT kullanımı: Unique constraint olan kolonlarda DISTINCT kullanmak gereksiz CPU harcar
- DISTINCT ve agregasyon karıştırma:
SELECT DISTINCT kolon, COUNT(*)yazmak anlamlı sonuç vermez
-- YANLIS: ID varken DISTINCT anlamsız
SELECT DISTINCT musteri_id, ad, soyad FROM musteriler;
-- DOGRU: ID olmadan sehir bazlı benzersiz liste
SELECT DISTINCT il, ilce, sehir FROM musteriler;
-- YANLIS: DISTINCT ile toplam karıştırma
SELECT DISTINCT musteri_id, COUNT(*) as siparis FROM siparisler;
-- DOGRU: GROUP BY kullan
SELECT musteri_id, COUNT(*) as siparis FROM siparisler GROUP BY musteri_id;
Sonuç
DISTINCT, SQL dünyasının çok basit görünen ama derinlemesine anlaşıldığında son derece güçlü bir aracıdır. Tekrar eden satırları elemek, benzersiz değerleri saymak, JOIN sonrası ortaya çıkan veri şişkinliğini gidermek ve log analizleri yapmak gibi günlük sysadmin görevlerinin tam ortasında yer alır.
Özetlemek gerekirse:
- Sadece benzersiz değerleri görmek istiyorsanız
SELECT DISTINCTkullanın - Hem gruplama hem de hesaplama yapacaksanız
GROUP BYtercih edin - Büyük tablolarda daima
EXPLAINile sorgu planını kontrol edin DISTINCTyapılacak kolonlara uygun indeksler ekleyin- NULL değerlerin tek bir NULL olarak ele alındığını unutmayın
COUNT(DISTINCT kolon)ile benzersiz kayıt sayısını hızlıca hesaplayın
Veritabanı yönetiminde temiz ve doğru veri her şeyin temelidir. DISTINCT bu temizliği sorgu seviyesinde sağlamanın en pratik yoludur. Ancak unutmayın, gerçek çözüm veri girişinde tekrarlara izin vermemek, yani doğru constraint ve indeks tasarımıdır. DISTINCT ise bu sorunla karşılaştığınızda en güvenilir ilk yardım aracınız olmaya devam edecektir.
