İki tür tekrar
- Tam tekrar: Tüm sütunlar aynı.
SELECT DISTINCTyeterli. - Mantıksal tekrar: Aynı varlık, farklı yazımlar veya sürümler (ör. aynı e-posta, biri büyük harfli). Bir anahtar tanımlayıp her anahtar için bir satır seçmek gerekir.
WITH s AS (
SELECT *,
LOWER(TRIM(email)) AS email_norm,
ROW_NUMBER() OVER (PARTITION BY LOWER(TRIM(email)) ORDER BY id) AS rn
FROM raw_signups
)
SELECT id, email_norm, TRIM(full_name) AS ad, signup_date
FROM s WHERE rn = 1;
“Hangi kayıt kalsın?” sorusu iş kuralıdır: en yeni, en eksiksiz, ilk gelen… ORDER BY ifadesi bu kuralı kodlar.
Tablodan silerek tekilleştirme
DELETE FROM raw_signups WHERE rowid NOT IN ( SELECT MIN(rowid) FROM raw_signups GROUP BY id, full_name, email, city, signup_date ); SELECT COUNT(*) FROM raw_signups;
PostgreSQL'de ctid, diğerlerinde CTE + ROW_NUMBER ile benzer yapılır.
Derinleş: “hangi kayıt kalır?” kuralı
Tekilleştirmenin teknik kısmı kolaydır: ROW_NUMBER() OVER (PARTITION BY anahtar ORDER BY kural) ve rn = 1. Zor kısım iki iş kararıdır: anahtar (iki kayıt ne zaman aynı varlıktır?) ve hayatta kalma kuralı (survivorship: hangisi kalır?).
Anahtar seçimi
Ad soyad iyi bir anahtar değildir: Lezzet Kurye'de 185 ad–şehir çifti birden fazla müşteri kaydında geçiyor ve bunların çoğu gerçekten farklı kişiler (yaygın adlar). E-posta daha güçlüdür ama onun da tuzağı var: raw_signups tablosunda can.kaya@gmail.com iki farklı id (8 ve 33) altında görünüyor. E-postayı anahtar yaparsan bu iki kaydı birleştirirsin; doğru olup olmadığını kaynağa sormak gerekir.
Hayatta kalma kuralı
| Kural | ORDER BY | Ne zaman? |
|---|---|---|
| İlk gelen kalır | created_at, id | Müşteri kimliğinin ilk kaydı esas |
| En yeni kalır | updated_at DESC, id DESC | Adres, telefon gibi güncel bilgi önemli |
| En eksiksiz kalır | CASE WHEN city IS NULL THEN 1 ELSE 0 END, id | Kaynaklar farklı alanları dolduruyor |
| Güvenilir kaynak kalır | CASE source WHEN 'crm' THEN 1 ELSE 2 END | Birden çok sistem birleşiyor |
ORDER BY'ın sonunda her zaman benzersiz bir sütun (eşitlik bozucu) bulunsun; yoksa aynı sorgu iki çalıştırmada farklı satırı seçebilir.
RANK tuzağı
WITH s AS (
SELECT LOWER(TRIM(email)) AS e,
ROW_NUMBER() OVER (PARTITION BY LOWER(TRIM(email)) ORDER BY id) AS rn,
RANK() OVER (PARTITION BY LOWER(TRIM(email)) ORDER BY id) AS rk
FROM raw_signups
)
SELECT SUM(CASE WHEN rn = 1 THEN 1 ELSE 0 END) AS row_number_ile,
SUM(CASE WHEN rk = 1 THEN 1 ELSE 0 END) AS rank_ile,
COUNT(DISTINCT e) AS tekil_email
FROM s;
39 tekil e-posta var; ROW_NUMBER 39 satır bırakır, RANK ise 51. Tam tekrar satırlar aynı id'ye sahip olduğu için RANK hepsine 1 verir ve tekrarlar yerinde kalır.
Birleştirmenin bedeli
Kanat Havayolları'nda 33 e-posta birden fazla yolcu kaydında geçiyor. En eski kaydı (en küçük joined_date) bırakırsak 34 yolcu kaydı birleşir ve bunlara bağlı 172 rezervasyonun passenger_id'si kalan kayda taşınmalıdır. Tekilleştirme yalnızca bir tabloyu değil, ona bakan tüm tabloları etkiler.
SELECT DISTINCT ON (email) ... ORDER BY email, joined_date. BigQuery ve Snowflake: QUALIFY ROW_NUMBER() OVER (...) = 1. Diğerlerinde CTE + ROW_NUMBER kalıbı her yerde çalışır.