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

Bağlam geçişi (context transition)

Satır bağlamı filtre bağlamına nasıl dönüşür? Ölçü çağırmak neden farklı sonuç verir?

Uygulamada aç, 10 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ç.

Tanım

Bağlam geçişi (context transition), CALCULATE bir satır bağlamının içinde çalıştığında olur: geçerli satırın tüm sütun değerleri filtreye dönüşür. Ölçüler her zaman gizli bir CALCULATE ile sarılı olduğundan, bir yineleyicinin içinde ölçü çağırmak da bağlam geçişini tetikler.

Karşılaştır

A = SUMX(Musteriler, SUM(Satislar[Tutar]))
B = SUMX(Musteriler, CALCULATE(SUM(Satislar[Tutar])))
C = SUMX(Musteriler, [Toplam Satış])

Playground’da üçünü de çalıştırıp farkı gör.

Nerede işe yarar?

-- Müşteri başına ortalama satış (yalnızca alışveriş yapanlar)
Müşteri Ortalaması = AVERAGEX(VALUES(Satislar[MusteriNo]), [Net Satış])

-- 10.000 TL üstü alışveriş yapan müşteri sayısı
Değerli Müşteri = COUNTROWS(FILTER(VALUES(Musteriler[Musteri]), [Net Satış] > 10000))
Dikkat: yinelenen satırlarBağlam geçişi satırın tüm sütunlarını filtreye çevirir. Tabloda birebir aynı iki satır varsa, her biri ikisinin toplamını görür ve sonuç iki kat sayılır. Bu yüzden yineleyiciyi mümkünse anahtarı olan bir tabloda ya da VALUES(Tablo[Anahtar]) üzerinde çalıştır.
PerformansBağlam geçişi her satır için yeni bir filtre bağlamı kurar. 1 milyon satırlık olgu tablosu üzerinde SUMX(Satislar, [Ölçü]) yavaştır; boyut üzerinde (VALUES(Urunler[Urun])) dolaşmak çok daha hızlıdır.
Derinleş

Bağlam geçişi hangi satırları filtreler?

Satır bağlamı CALCULATE ile filtreye dönüştüğünde, mevcut satırın tüm sütunlarının değeri filtre olur. Satır benzersizse bu, tam olarak o satırı seçer. Ama tablo birbirinin aynısı iki satır içeriyorsa, bağlam geçişi ikisini birden seçer ve iteratör her birinde iki satırın toplamını görür: sonuç iki katına çıkar.

Bu modelde Satislar[SatirNo] her satırı benzersiz kıldığı için SUMX(Satislar, [Net Satış]) doğru sonuç verir. Ama anahtarı olmayan bir tabloda (ör. Excel’den gelen, satır numarası olmayan bir liste) aynı ölçü sessizce yanlış toplar. Olgu tablolarında benzersiz bir satır anahtarı tutmanın bir sebebi de budur.

Ürün Başına Satış (doğru düzey) =
SUMX(VALUES(Urunler[Urun]), [Net Satış])

İkinci kural: iteratörü sorunun düzeyinde döndür. “Her ürünün satışı” sorusu ürün listesinde döner; 3.000 satırlık olgu tablosunda dönmek hem yavaştır hem de yukarıdaki tekrar riskini taşır.

EARLIER nereden çıktı?Eski DAX kodlarında iç içe satır bağlamlarında dıştaki satıra erişmek için EARLIER görürsün. Bugün aynı iş, dış satırın değerini bir VAR’a alarak çok daha okunur yapılır.
Uzmanlaş

Bağlam geçişinin bedeli: her satırda bir CALCULATE

Satış müdürü, "Temsilci Performansı" sayfasının yavaş açıldığından yakınıyor. Sayfadaki bir kart şu ölçüyü kullanıyor: SUMX(Satislar, [Net Satış]). Sonuç doğru: 1.250.772,45 ₺. Peki motor bu sayıya nasıl ulaşıyor?

  1. SUMX, Satislar tablosunun 1.620 satırını tek tek dolaşır. Her satırda bir satır bağlamı açılır.
  2. [Net Satış] bir ölçüdür; her ölçü gizli bir CALCULATE ile sarılıdır. CALCULATE satır bağlamını görünce bağlam geçişini çalıştırır: satırın 17 sütununun değeri tek tek filtreye dönüşür.
  3. Bu yeni filtre bağlamında ölçünün kendi filtresi (Satislar[Durum] <> "İptal") eklenir ve SUM hesaplanır.
  4. Bu iş 1.620 kez tekrarlanır ve sonuçlar toplanır.
YazımDolaşılan satırBağlam geçişiSonuç
SUMX(Satislar, [Net Satış])1.6201.6201.250.772,45
SUMX(VALUES(Urunler[Urun]), [Net Satış])40401.250.772,45
[Net Satış]yok01.250.772,45

Üç yazım da aynı sayıyı verir; maliyetleri çok farklıdır. Power BI'ın motorunda basit toplamalar depolama motorunda (storage engine) sıkıştırılmış sütunlar üzerinde çalışır ve çok hızlıdır. Satır satır bağlam geçişi gerektiren ifadeler ise işi formül motoruna (formula engine) taşır. Bin satırda fark görünmez, on milyon satırda saniyelerle ölçülür.

Nasıl ölçersin? Rapor görünümünde Optimize şeridi → Performans Analizi (Performance analyzer) → kaydı başlat, görselleri yenile, görselin sorgusunu kopyala. Sorguyu DAX Studio'da Server Timings açık çalıştır. Formül motoru süresi yüksek, depolama motoru sorgu sayısı fazlaysa bağlam geçişinden şüphelen.

Kuralİteratörü sorunun tanecik düzeyinde (müşteri, ürün, ay) döndür. Olgu tablosunda dolaşman gerekiyorsa ölçü çağırma, sütun ifadesi yaz: SUMX(Satislar, Satislar[Adet] * Satislar[BirimFiyat]) bağlam geçişi içermez.

İteratör içinde ölçü çağırmak: üç sessiz fark

1. Boş satırlar ortalamadan düşer

Alıcı Ortalaması =
AVERAGEX(Musteriler, [Net Satış])

Kayıtlı Müşteri Ortalaması =
DIVIDE([Net Satış], COUNTROWS(Musteriler))

Birincisi 9.196,86 ₺, ikincisi 8.338,48 ₺ döndürür. Musteriler'de 150 satır var, ama 14 müşteri hiç alışveriş yapmamış. Onların [Net Satış] değeri BLANK'tir ve AVERAGEX boşları atlar; yani 150'ye değil 136'ya böler. Hangisi doğru? Soru "alışveriş yapan müşteri başına" ise birincisi, "kayıtlı müşteri başına" ise ikincisi. Ölçünün adı bu farkı açıkça söylemeli.

2. Ölçü yerine çıplak toplama yazmak

Yanlış Değerli Müşteri =
COUNTROWS(FILTER(Musteriler, SUM(Satislar[Tutar]) > 10000))

Sonuç 150'dir. FILTER'ın satır bağlamı filtre değildir; her müşteri satırında SUM tüm tabloyu (1.346.189,85 ₺) görür ve koşul herkes için doğru çıkar. Aynı koşulu [Net Satış] ile yazınca bağlam geçişi devreye girer ve sonuç 42 olur. Yanlış sürüm hata vermez, yalnızca yanlış sayı üretir. Kod incelemesinde ilk aranacak şey budur.

3. Ölçünün kendi filtresi satırı kurtarmaz

[Net Satış] içinde Satislar[Durum] <> "İptal" var. Bu filtre, Durum sütunundaki dış filtrenin yerine geçer. Bir matrise Kanal ve Durum koyarsan "Web / İptal" hücresinde 457.330,90 ₺ görürsün: bu, Web'in tüm net satışıdır. Okuyan kişi bunu "iptal edilen tutar" sanar.

Oysa aynı ölçü SUMX(Satislar, [Net Satış]) içinde çağrıldığında iptal satırları boş döner. Neden? Bağlam geçişi yalnızca Durum'u değil SatirNo dahil bütün sütunları filtreler. Durum filtresinin değiştirilmesi, satırın kendisini seçen SatirNo filtresini kaldırmaz; o satır iptal olduğu için kesişim boştur. Aynı ölçü, aynı tabloda iki farklı davranış gösterir.

Durum kırılımı olan raporlarÖlçünün filtresini KEEPFILTERS ile yaz: CALCULATE([Toplam Satış], KEEPFILTERS(Satislar[Durum] <> "İptal")). KEEPFILTERS dış filtreyi silmez, onunla kesişir; İptal hücresi boş kalır ve satır görselden düşer.

Yinelenen satırlar: sayı nasıl şişer?

Muhasebe, ERP'den "günlük ürün satışı" adlı bir Excel dosyası gönderiyor: Tarih, UrunNo ve Tutar sütunları var, satır numarası yok. Aynı gün aynı ürün aynı tutarla iki kez satıldıysa dosyada birebir aynı iki satır bulunur. Bu tabloda SUMX(Tablo, [Ölçü]) çalıştırırsan ne olur? Modelde bunu taklit etmek için bağlam geçişinin yalnızca bu üç sütunu filtrelediğini varsayalım:

Anahtarsız Tablo Taklidi =
SUMX(
    Satislar,
    CALCULATE(
        SUM(Satislar[Tutar]),
        ALLEXCEPT(Satislar, Satislar[Tarih], Satislar[UrunNo], Satislar[Tutar])
    )
)

Gerçek toplam 1.346.189,85 ₺; bu ölçü 1.387.343,85 ₺ döndürür (%3,1 fazla). Satislar'da bu üç sütuna göre 24 yinelenen satır var. İkiz satırlardan her biri, bağlam geçişinde ikisini birden seçer ve toplam iki kez sayılır. Yalnızca Tarih ve UrunNo kalsaydı 65 tekrar oluşur, sonuç 1.432.133,80 ₺'ye (%6,4 fazla) çıkardı. Hata toplamda küçük bir sapma gibi görünür; kimse fark etmez.

ÇözümNe zaman?
Power Query'de Dizin sütunu (Index column) ekleKaynakta anahtar yoksa; ilk tercih
İteratörü boyut anahtarında döndür: VALUES(Urunler[UrunNo])Soru zaten ürün ya da müşteri düzeyindeyse
Ölçü yerine sütun ifadesi: SUMX(T, T[Tutar])Satır düzeyinde hesap yeterliyse; bağlam geçişi hiç yok
Test: COUNTROWS(T) = DISTINCTCOUNT(T[Anahtar])Her yeni olgu tablosunda, DAX sorgusuyla

Bağlam geçişi bütün satır bağlamlarını dönüştürür

İç içe iteratörlerde bağlam geçişi yalnızca en içteki satırı değil, açık olan tüm satır bağlamlarını filtreye çevirir. Şu sorguda dış satır bir segment, iç satır bir müşteridir:

EVALUATE
ADDCOLUMNS(
    VALUES(Musteriler[Segment]),
    "Değerli", COUNTROWS(FILTER(VALUES(Musteriler[MusteriNo]), [Net Satış] > 10000))
)

İçteki VALUES(Musteriler[MusteriNo]) segment satırına göre süzülmez; her segmentte 150 müşterinin hepsini döndürür. Ama [Net Satış] çağrıldığında hem müşteri hem segment filtreye döner. Başka segmentteki müşteri için kesişim boştur ve koşul yanlış çıkar. Sonuç yine doğrudur: Premium 21, Kurumsal 14, Bireysel 7. Doğru ama pahalı: her segmentte 150 müşteri dolaşılır. Dış ifadeyi CALCULATE ile sarmak (bir sonraki alıştırma) listeyi baştan segmente göre süzer ve gereksiz geçişleri önler.

ALLSELECTED iteratörün içinde

[Seçime Göre Pay %] şöyle tanımlı: DIVIDE([Net Satış], CALCULATE([Net Satış], ALLSELECTED())). Bir analist "her kanalda kategorilerin ortalama payı"nı istiyor ve şunu yazıyor: AVERAGEX(VALUES(Kategoriler[Kategori]), [Seçime Göre Pay %]). Beklentisi her kanal satırında %14,29 (yedi payın ortalaması). Görselde ise Web %5,22, Mobil %6,85, Mağaza %2,21 çıkıyor.

Mekanizma: ALLSELECTED(), görselin kendi satırlarından gelen filtreleri (burada Kanal) ve bağlam geçişinden gelen kategori filtresini kaldırır; yalnızca görselin dışından gelen filtreleri (dilimleyiciler, sayfa filtreleri) bırakır. Payda Web'in toplamı değil, görselin genel toplamıdır. Yedi payın ortalaması Web'in genel paydaki yerinin (%36,56) yedide biri olur.

Kanal İçi Ortalama Kategori Payı =
VAR KanalSatis = [Net Satış]
RETURN
    AVERAGEX(VALUES(Kategoriler[Kategori]), DIVIDE([Net Satış], KanalSatis))

Doğru yazım paydayı iteratörden önce bir değişkene alır; her kanal satırında %14,29 çıkar. Kural: ALLSELECTED kullanan ölçüleri doğrudan görselde kullan, başka bir iteratörün içinden çağırma. Çağırman gerekiyorsa davranışı DAX sorgu görünümünde ayrıca test et.

Hesaplanmış sütunda bağlam geçişi

Ürün tablosuna "ürünün toplam satışı" sütunu eklenecek. Hesaplanmış sütunda satır bağlamı vardır, filtre bağlamı yoktur. Aşağıdaki sorgu iki yazımı yan yana gösterir (ADDCOLUMNS burada hesaplanmış sütunun yerini tutar):

EVALUATE
TOPN(
    3,
    ADDCOLUMNS(
        Urunler,
        "Çıplak SUM", SUM(Satislar[Tutar]),
        "CALCULATE ile", CALCULATE(SUM(Satislar[Tutar]))
    ),
    [CALCULATE ile]
)

"Çıplak SUM" her üründe 1.346.189,85 ₺ (tüm tablo) gösterir. "CALCULATE ile" sütununda ürün satırı filtreye döner, filtre ilişki üzerinden Satislar'a iner: 27" Monitör için 101.986 ₺.

  • Döngüsel bağımlılık (circular dependency): Özellikle anahtarı olmayan bir tabloda, CALCULATE kullanan ikinci bir hesaplanmış sütun eklediğinde bu hatayı alabilirsin. Bağlam geçişi tablonun bütün sütunlarını filtrelediği için her yeni sütun diğerine bağımlı görünür. Çözüm: CALCULATE içinde ALLEXCEPT(Tablo, Tablo[Anahtar]) ile yalnızca anahtarın filtresini tutmak ya da hesabı ölçüye taşımak.
  • Olgu tablosunda CALCULATE'li sütun: 1.620 satırda sorun yok; 50 milyon satırda yenileme süresini katlar. Bu tür hesaplar Power Query'de ya da kaynakta yapılmalı.
  • Donma: Hesaplanmış sütun yalnızca yenilemede hesaplanır, dilimleyiciye tepki vermez. "Seçili yıldaki satış" gibi sorular ölçü ister.

Kontrol listesi

  1. İteratör hangi tabloda dönüyor? Sorunun düzeyiyle aynı mı?
  2. İçeride ölçü mü, çıplak toplama mı var? Çıplaksa satır bağlamı filtreye dönüşmüyor demektir.
  3. Dolaşılan tablonun benzersiz bir anahtarı var mı? Yoksa sonuç şişebilir.
  4. Ortalama alıyorsan boş satırların atlanması isteniyor mu?
  5. İçeride ALLSELECTED kullanan bir ölçü çağrılıyor mu? Paydayı değişkene al.
  6. Olgu tablosunda hesaplanmış sütun + CALCULATE var mı? Power Query'ye ya da ölçüye taşı.
Alıştırmalara geç (10)