Ana içeriğe geç
Kesit Power BI
İleri · Fabric

Fabric’te yıldız şema: SCD Tip 2, köprü tablolar ve Delta bakımı

Lakehouse ve ambarda boyut-olgu tasarımı, vekil anahtar, yavaş değişen boyutlar (Tip 1, Tip 2) ve olgu satırını doğru sürüme bağlamak, köprü tablo, T-SQL ile MERGE ve CTAS, OPTIMIZE, V-Order ve VACUUM, DAX’ta “o tarihteki değer”.

Uygulamada aç, 6 alıştırmayı çözÜcretsiz, hesap gerekmez, kurulum yok.

Bu sayfa dersin okuma sürümüdür. Metin bir kutuyu çalıştırmanı ya da adıma geçmeni söylüyorsa dersi uygulamada aç.

“Veri modelleme: yıldız şema ve ilişkiler” dersinde yıldız şemayı Power BI modelinin içinde kurdun. Fabric’te aynı tasarımı bir katman aşağıya, lakehouse ya da ambar tablolarına indirirsin: Direct Lake modelde Power Query adımı yoktur, hesaplanmış sütun ve tablo desteği de sınırlı ve yeni olduğu için (önizleme) şema, Delta tablolar yazılırken hazır olmalıdır. Bu ders o tasarımın üç zor parçasını işliyor: tarih boyunca değişen boyutlar, çok değerli boyutlar ve tabloların fiziksel bakımı.

Boyut ve olgu tablolarını tasarlamak

Vekil anahtar (surrogate key)

Kaynak sistemin anahtarı (MusteriNo) bir iş anahtarıdır. Ambarda boyuta ayrıca anlamsız, artan bir tamsayı verilir: MusteriAnahtari. Nedenleri:

Fabric ambarında IDENTITY sütunları destekleniyor; lakehouse’ta Spark ile row_number() ya da en büyük anahtar + 1 yöntemi kullanılır. Ambarda birincil ve yabancı anahtar kısıtları yalnızca NOT ENFORCED olarak tanımlanabilir: belgeleme ve sorgu iyileştiricisi içindir, benzersizliği yükleme kodun sağlamalıdır.

Yavaş değişen boyutlar: Tip 1 ve Tip 2

Bir müşteri 2024 başında Bireysel segmentten Premium’a geçti. 2023 satışları hangi segmentte raporlanmalı?

TürNe yapar?Sonuç
Tip 1Eski değerin üzerine yazarGeçmiş satışlar da bugünkü segmentte görünür. Basit; yazım düzeltmeleri için doğru.
Tip 2Eski satırı kapatır, yeni sürüm satırı eklerHer satış, gerçekleştiği andaki segmentte görünür. Tarihsel doğruluk.

Tip 2 boyutta her satır bir sürümdür ve şu sütunları taşır: MusteriAnahtari (vekil, sürüme özgü), MusteriNo (iş anahtarı), izlenen nitelikler, GecerliBaslangic, GecerliBitis (açık sürümde 9999-12-31) ve Guncel bayrağı (1/0).

MusteriAnahtariMusteriNoSegmentGecerliBaslangicGecerliBitisGuncel
1044Bireysel2022-01-012023-12-310
3884Premium2024-01-019999-12-311

Hangi niteliklerin Tip 2 olacağını iş kararı belirler: segment, bölge, temsilci evet; ad soyad yazım düzeltmesi hayır (Tip 1). Her niteliği Tip 2 yapmak boyutu gereksiz yere büyütür.

T-SQL: MERGE ile SCD2, CTAS ile olgu

Ambarda MERGE genel kullanımda. Önce değişen müşterinin açık sürümünü kapat ve yeni müşterileri ekle:

MERGE dbo.DimMusteri AS h
USING stg.Musteri AS k
   ON h.MusteriNo = k.MusteriNo AND h.Guncel = 1
WHEN MATCHED AND h.Segment <> k.Segment THEN
    UPDATE SET h.GecerliBitis = DATEADD(day, -1, CAST(GETDATE() AS date)),
               h.Guncel = 0
WHEN NOT MATCHED BY TARGET THEN
    INSERT (MusteriNo, Musteri, Segment, GecerliBaslangic, GecerliBitis, Guncel)
    VALUES (k.MusteriNo, k.Musteri, k.Segment, CAST(GETDATE() AS date), '9999-12-31', 1);

-- Kapatılan sürümlerin yerine yeni açık sürüm
INSERT INTO dbo.DimMusteri (MusteriNo, Musteri, Segment, GecerliBaslangic, GecerliBitis, Guncel)
SELECT k.MusteriNo, k.Musteri, k.Segment, CAST(GETDATE() AS date), '9999-12-31', 1
FROM stg.Musteri AS k
WHERE NOT EXISTS (SELECT 1 FROM dbo.DimMusteri AS h
                  WHERE h.MusteriNo = k.MusteriNo AND h.Guncel = 1);

Olgu yüklenirken her satır, satış tarihinde geçerli olan sürümün vekil anahtarını almalıdır. CREATE TABLE AS SELECT (CTAS) bunu tek adımda yapar:

CREATE TABLE dbo.FactSatis AS
SELECT s.SiparisNo, s.SatirNo, s.Tarih, s.UrunNo, s.Adet, s.Tutar,
       COALESCE(m.MusteriAnahtari, -1) AS MusteriAnahtari
FROM stg.Satis AS s
LEFT JOIN dbo.DimMusteri AS m
       ON m.MusteriNo = s.MusteriNo
      AND s.Tarih BETWEEN m.GecerliBaslangic AND m.GecerliBitis;

Bu yapıldıysa Power BI tarafı kolaydır: FactSatis[MusteriAnahtari] ile DimMusteri[MusteriAnahtari] arasında normal bir ilişki kurarsın ve her satış kendiliğinden doğru segmentte görünür. “Bugünkü segmente göre” analiz de istenirse boyuta bir Tip 1 sütunu (GuncelSegment) eklenir.

Sık hataOlguyu iş anahtarıyla (MusteriNo) Tip 2 boyuta bağlamak. Boyutta aynı numaradan birden çok satır olduğu için ilişki çoka-çok olur ve satışlar sürüm sayısı kadar çoğalabilir.

Çok değerli boyut ve köprü tablo

Bir kampanya birden çok kategoriyi kapsar, bir kategori birden çok kampanyada yer alır. Bunu olgu tablosuna sütun olarak koyamazsın. Çözüm bir köprü tablodur: KampanyaKategori(Kampanya, Kategori). Modelde köprü, iki boyut arasında durur; filtrenin Kampanya → Köprü → Kategoriler yönünde akması için köprü ile Kategoriler arasındaki ilişki çift yönlü yapılır ya da ölçüde TREATAS kullanılır. Ayrıntı “Tablo fonksiyonları, kümeler ve sanal ilişkiler” dersinde. İki önemli kural:

Bizim modelde kampanya tablosu yok; köprü mantığını DAX içinde küçük bir tabloyla deneyebilirsin:

Okula Dönüş Satışı =
VAR Kopru =
    SELECTCOLUMNS(
        { ("Okula Dönüş", "Kitap"), ("Okula Dönüş", "Elektronik"), ("Okula Dönüş", "Giyim"),
          ("Yaz Festivali", "Spor"), ("Yaz Festivali", "Giyim") },
        "Kampanya", [Value1], "Kategori", [Value2]
    )
VAR Kategoriler_ = SELECTCOLUMNS(FILTER(Kopru, [Kampanya] = "Okula Dönüş"), "Kategori", [Kategori])
RETURN CALCULATE([Net Satış], KEEPFILTERS(TREATAS(Kategoriler_, Kategoriler[Kategori])))

KEEPFILTERS olmazsa matrisin her kategori satırında kampanyanın toplamı (681.355 ₺) görünür; çünkü TREATAS aynı sütundaki dış filtrenin üzerine yazar.

Delta bakımı: OPTIMIZE, V-Order, VACUUM

Her yükleme yeni Parquet dosyaları ekler. Küçük ve çok sayıda dosya hem Spark’ı hem Direct Lake’i yavaşlatır; Direct Lake’in kapasiteye göre dosya ve satır grubu sınırları (guardrails) da aşılabilir.

-- Spark SQL (lakehouse not defteri)
OPTIMIZE satislar VORDER;
VACUUM satislar RETAIN 168 HOURS;

-- Tabloda V-Order'ı kalıcı aç (yalnızca bundan sonraki yazmaları etkiler)
ALTER TABLE satislar SET TBLPROPERTIES ("delta.parquet.vorder.enabled" = "true");

Aynı işlemler lakehouse gezgininde tabloya sağ tık → Bakım ile de çalıştırılır; düzenli bakım için işlem hattındaki Lakehouse bakım etkinliği (önizleme) kullanılabilir. Ambar tablolarının bakımını Fabric büyük ölçüde kendisi yapar.

DAX tarafında SCD2: “o tarihteki değer”

En iyisi, yukarıdaki gibi olgunun doğru sürümün anahtarını taşımasıdır. Ama bazen elinde yalnızca iş anahtarı olan bir olgu ve ayrı bir geçmiş tablosu olur. O zaman her satış satırı için geçerli sürümü tarih aralığına göre bulursun. Bizim modelde Musteriler Tip 1 (yalnızca bugünkü segment) olduğu için geçmişi küçük bir tabloyla canlandıralım: 4 numaralı müşteri 2024’te Premium’a geçti, 8 numaralı müşteri Nisan 2024’te Premium’dan Bireysel’e indi.

EVALUATE
SELECTCOLUMNS(
    { (4, "Bireysel", DATE(2022, 1, 1), DATE(2023, 12, 31)),
      (4, "Premium",  DATE(2024, 1, 1), DATE(2099, 12, 31)),
      (8, "Premium",  DATE(2022, 1, 1), DATE(2024, 3, 31)),
      (8, "Bireysel", DATE(2024, 4, 1), DATE(2099, 12, 31)) },
    "MusteriNo", [Value1], "Segment", [Value2],
    "GecerliBaslangic", [Value3], "GecerliBitis", [Value4]
)

Ölçü, her satış satırında müşterinin o tarihte geçerli sürümünü arar; geçmişte kaydı olmayan müşteri için bugünkü segmenti kullanır:

Premium Satış (O Tarihte) =
VAR Gecmis =
    SELECTCOLUMNS(
        { (4, "Bireysel", DATE(2022, 1, 1), DATE(2023, 12, 31)),
          (4, "Premium",  DATE(2024, 1, 1), DATE(2099, 12, 31)),
          (8, "Premium",  DATE(2022, 1, 1), DATE(2024, 3, 31)),
          (8, "Bireysel", DATE(2024, 4, 1), DATE(2099, 12, 31)) },
        "MusteriNo", [Value1], "Segment", [Value2],
        "Bas", [Value3], "Bit", [Value4]
    )
RETURN
CALCULATE(
    SUMX(
        Satislar,
        VAR No = Satislar[MusteriNo]
        VAR Gun = Satislar[Tarih]
        VAR Surum = FILTER(Gecmis, [MusteriNo] = No && [Bas] <= Gun && [Bit] >= Gun)
        VAR Seg = IF(ISEMPTY(Surum), RELATED(Musteriler[Segment]), MAXX(Surum, [Segment]))
        RETURN IF(Seg = "Premium", Satislar[Tutar])
    ),
    Satislar[Durum] <> "İptal"
)

Sonuç: Tip 1 görünümde (Musteriler[Segment] = "Premium") 2023 Premium satışı 164.865 ₺, tarihsel görünümde 154.847 ₺. Fark tam olarak 4 numaralı müşterinin 2023’teki Bireysel dönemi (−10.594 ₺) ve 8 numaralı müşterinin 2023’teki Premium dönemidir (+576 ₺). Bu ölçü öğreticidir ama satır satır arama yaptığı için büyük olguda yavaştır; üretimde sürüm anahtarını yükleme sırasında olguya yaz.

Alıştırmalara geç (6)