Sorun
Bir ürünün fiyatı veya bir müşterinin şehri değişir. Geçmiş raporlar eski değeri mi, yeni değeri mi göstermeli?
| Tip | Davranış | Kullanım |
|---|---|---|
| SCD 1 | Eski değerin üzerine yazılır, tarihçe yok. | Yazım hatası düzeltme, önemsiz nitelikler |
| SCD 2 | Her değişiklik yeni satır; valid_from, valid_to, is_current. | Fiyat, adres, segment gibi analizde önemli nitelikler |
| SCD 3 | Önceki değer ayrı sütunda (previous_city). | Sadece bir önceki değer yeterliyse |
SCD 2'yi LEAD ile kurmak
WITH surumler AS (
SELECT product_id, price AS fiyat, '2000-01-01 00:00:00' AS gecerlilik FROM products
UNION ALL
SELECT product_id, new_price, updated_at FROM price_updates
)
SELECT product_id, fiyat,
gecerlilik AS valid_from,
COALESCE(LEAD(gecerlilik) OVER (PARTITION BY product_id ORDER BY gecerlilik), '9999-12-31') AS valid_to,
CASE WHEN LEAD(gecerlilik) OVER (PARTITION BY product_id ORDER BY gecerlilik) IS NULL THEN 1 ELSE 0 END AS is_current
FROM surumler
WHERE product_id IN (SELECT product_id FROM price_updates)
ORDER BY product_id, valid_from;
Belirli bir tarihteki fiyatı bulmak: WHERE :tarih >= valid_from AND :tarih < valid_to.
Derinleş: SCD tiplerini seçmek ve zaman noktasına göre birleştirmek
Boyut tablosundaki bir nitelik değiştiğinde ne olacağı bir iş sorusudur: “Geçen yılın raporu, geçen yılki değeri mi göstermeli?” Cevaba göre tip seçilir:
| Tip | Değişiklikte | Tarihçe | Artı / eksi |
|---|---|---|---|
| 0 | Hiç değişmez | — | Doğum tarihi gibi sabit nitelikler |
| 1 | Üzerine yazılır | Yok | Basit; geçmiş raporlar sessizce değişir |
| 2 | Eski satır kapanır, yeni satır açılır | Tam | Doğru geçmiş; tablo büyür, birleştirme tarih aralığı ister |
| 3 | Önceki değer ayrı sütuna taşınır | Yalnızca bir önceki | “Eski bölge / yeni bölge” karşılaştırmaları için yeterli |
Uygulamada aynı boyutta tipler karışır: müşterinin adı yazım düzeltmesinde Tip 1, segmenti Tip 2 olabilir.
Zaman noktasına göre birleştirme (point-in-time join)
SCD 2'nin değeri, olay tablosunu olay anında geçerli satırla birleştirince ortaya çıkar. Ekim–Aralık 2024'te fiyatı değişen ürünlerin satışlarını, sipariş günündeki liste fiyatıyla ve bugünkü (güncel) fiyatla ayrı ayrı değerleyelim:
WITH s AS (
SELECT product_id, price AS fiyat, '2000-01-01 00:00:00' AS g FROM products
UNION ALL
SELECT product_id, new_price, updated_at FROM price_updates
),
dim AS (
SELECT product_id, fiyat, g AS valid_from,
COALESCE(LEAD(g) OVER (PARTITION BY product_id ORDER BY g), '9999-12-31') AS valid_to,
CASE WHEN LEAD(g) OVER (PARTITION BY product_id ORDER BY g) IS NULL THEN 1 ELSE 0 END AS is_current
FROM s
)
SELECT ROUND(SUM(oi.quantity * d.fiyat)) AS o_gunku_fiyatla,
ROUND(SUM(oi.quantity * c.fiyat)) AS guncel_fiyatla,
COUNT(*) AS satir
FROM orders o
JOIN order_items oi ON oi.order_id = o.order_id
JOIN dim d ON d.product_id = oi.product_id
AND o.order_date >= d.valid_from AND o.order_date < d.valid_to
JOIN dim c ON c.product_id = oi.product_id AND c.is_current = 1
WHERE o.order_date >= '2024-10-01'
AND oi.product_id IN (SELECT product_id FROM price_updates);
Aynı 112 satış satırı, o günkü fiyatla 105.688 TL, güncel fiyatla 108.502 TL ediyor. Tip 1 kullansaydın geçmiş dönemin değeri sessizce 2.814 TL değişirdi.
valid_from <= t < valid_to. Hem başlangıç hem bitişi dahil eden BETWEEN kullanırsan değişiklik anındaki olay iki satırla eşleşir ve satış iki kez sayılır.SCD 2 güncelleme adımları
- Kaynaktaki değeri hedefteki güncel satırla karşılaştır (çok sütunda
hashile). - Değişenlerde güncel satırı kapat:
valid_to = değişiklik anı,is_current = 0. - Yeni değerle
valid_to = '9999-12-31',is_current = 1satırı ekle. - Test: her anahtar için tam olarak bir güncel satır var mı, aralıklar çakışıyor mu?