Tablo döndüren ifadeler bir “değer”dir
DAX'ta bir tablo ifadesi değişkende tutulabilir, filtre olarak verilebilir, satırları sayılabilir. İleri seviyenin anahtarı, tabloları kümeler gibi düşünmektir:
| Fonksiyon | Küme işlemi | Örnek soru |
|---|---|---|
INTERSECT(A, B) | Kesişim | Hem 2023 hem 2024'te alışveriş yapan müşteriler |
EXCEPT(A, B) | Fark | 2023'te alıp 2024'te almayan müşteriler (kayıp) |
UNION(A, B) | Birleşim (tekrarlar kalır) | İki listenin birleşimi; DISTINCT ile tekilleştir |
CROSSJOIN(A, B) | Kartezyen çarpım | Tüm kanal × yıl kombinasyonları |
GENERATE(A, ifade) | A'nın her satırı için ifadenin döndürdüğü satırlar | Her kategori için ilk 3 ürün |
TREATAS(tablo, sütun…) | Soy bağını değiştirir | Bağlantısız tablodaki değerleri modeldeki bir sütuna filtre olarak uygulamak |
Soy bağı (lineage)
VALUES(Satislar[MusteriNo]) tek sütunlu bir tablodur ve sütunun modeldeki Satislar[MusteriNo] olduğunu “hatırlar”. Bu tablo CALCULATE'e verildiğinde Power BI hangi sütunu süzeceğini soy bağından bilir. {1, 2, 3} gibi elle yazılmış bir tablonun soy bağı yoktur; filtre olarak kullanılamaz. TREATAS soy bağını sonradan verir:
Kitap ve Spor = CALCULATE([Net Satış], TREATAS({"Kitap", "Spor"}, Kategoriler[Kategori]))
Sanal ilişki
Modelde ilişki olmayan iki tablo arasında, hesap anında ilişki kurmak. Örneğin bağlantısız KarsiUrunler tablosundaki seçimi Urunler'e uygulamak:
B Ürünleri Satışı =
CALCULATE(
[Net Satış],
TREATAS(VALUES(KarsiUrunler[KarsiUrunNo]), Urunler[UrunNo])
)
Alternatifleri: INTERSECT ile ya da FILTER(ALL(Urunler[UrunNo]), Urunler[UrunNo] IN VALUES(KarsiUrunler[KarsiUrunNo])). TREATAS genellikle en hızlı ve en okunur olanıdır. Sanal ilişki, hedefleri farklı tanecikte tutan bütçe tablolarında da sık kullanılır.
Çoka-çok ve köprü tablolar
Bir müşterinin birden çok segmente, bir ürünün birden çok kampanyaya ait olduğu durumlarda köprü (bridge) tablo kullanılır: Musteri ← KopruMusteriSegment → Segment. Filtrenin segmentten satışa ulaşması için köprü ile müşteri arasındaki ilişki çift yönlü olmalı ya da ölçüde CROSSFILTER(…, BOTH) kullanılmalıdır. Power BI'ın doğrudan “çoka-çok” kardinalitesi de vardır (iki tarafta da tekrar eden anahtar), ama sonuçları toplamlarda şaşırtıcı olabilir: bir satış iki segmentte sayılır, toplam ise bir kez sayar. Raporda bunun açıklanması gerekir.
FILTER(Satislar, …) ile sanal ilişki kurmak yavaştır. Sanal ilişkiyi boyut tablosunun anahtarı üzerinden (TREATAS(…, Urunler[UrunNo])) kur.SUMMARIZE ile gruplama, ADDCOLUMNS ile hesaplama
SUMMARIZE’ın içinde hesaplanmış sütun ("Satış", [Net Satış]) tanımlamak çalışır ama önerilmez: SUMMARIZE bu sütunları beklenmedik bir filtre bağlamında hesaplayabilir ve sonuç, özellikle aynı tablodan birden çok sütunla gruplandığında yanıltıcı olur. Güvenli kalıp, gruplamayı SUMMARIZE’a, hesaplamayı ADDCOLUMNS’a bırakmaktır:
EVALUATE
ADDCOLUMNS(
SUMMARIZE(Satislar, Urunler[Kategori]),
"Satış", [Net Satış]
)
ADDCOLUMNS her satır için ölçüyü bağlam geçişiyle çağırır; yani her kategori satırında [Net Satış] o kategorinin filtresiyle hesaplanır. Yalnızca sorgu yazıyorsan SUMMARIZECOLUMNS daha da iyidir: gruplama ile hesaplamayı tek adımda yapar ve tüm ölçüleri BLANK olan satırları otomatik atar.
SUMMARIZE: yalnızca var olan kombinasyonlar
Kanal müdürü soruyor: "Hangi ürün hangi kanalda hiç satılmadı?" Bu soru, iki tablo fonksiyonu arasındaki temel farkı gösterir:
| İfade | Satır | Anlamı |
|---|---|---|
VALUES(Urunler[Urun]) | 40 | Katalogdaki tüm ürünler |
SUMMARIZE(Satislar, Urunler[Urun]) | 38 | Satislar'da en az bir satırı olan ürünler |
SUMMARIZE(Satislar, Urunler[Urun], Satislar[Kanal]) | 113 | Gerçekleşmiş ürün-kanal çiftleri |
CROSSJOIN(VALUES(Urunler[Urun]), VALUES(Satislar[Kanal])) | 120 | Olası tüm çiftler |
SUMMARIZE olgu tablosunu tarar ve satırların gerçekten içerdiği kombinasyonları döndürür. Gruplama sütunlarını çoka-bir ilişkiler boyunca "bir" tarafından alabilir (Urunler[Urun]); ters yönde alamaz. CROSSJOIN ise tabloları birbiriyle çarpar ve veride hiç olmayan çiftleri de üretir. Aradaki 7 çift sorunun cevabıdır:
EVALUATE
FILTER(
CROSSJOIN(VALUES(Urunler[Urun]), VALUES(Satislar[Kanal])),
ISBLANK([Net Satış])
)
Sonuç: Robot Süpürge Web'de hiç satılmamış; Koşu Bandı ve Gurme Lokum Kutusu ise hiçbir kanalda. FILTER her çiftte [Net Satış]'ı çağırır; bağlam geçişi hem ürünü hem kanalı filtreye çevirir.
SUMMARIZECOLUMNS ve boş satırlar
SUMMARIZECOLUMNS, ifadelerinin hepsi BLANK olan satırları sonuçtan atar. Görsellerin "boş satırı göstermeme" davranışı buradan gelir. Bu kural küçük bir değişiklikle sonucun boyutunu değiştirir:
| Sorgu (Urunler[Urun] × Tarih[Yil]) | Satır | Neden? |
|---|---|---|
"S", [Net Satış] | 111 | Satışı olmayan ürün-yıl çiftleri atılır |
"S", [Net Satış] + 0 | 120 | Hiçbir satır boş değil; 40 × 3 çiftin hepsi gelir |
"S", [Net Satış], "K", [Kayıtlı Müşteri] | 120 | İkinci ölçü ürün filtresinden etkilenmez, hiç boş olmaz |
Küçük modelde 9 fazladan satır zararsızdır. Müşteri × ürün × gün kırılımında ise "+0" ya da hiç boş olmayan bir ölçü, milyonlarca satırlık bir sonuç üretir; görsel yavaşlar ya da veri sınırına takılır. Sıfırı göstermen gerekiyorsa bunu yalnızca gereken görselde, sınırlı bir eksende yap.
Gerçek Power BI'da, bir ifadeyi boşluk kontrolünden çıkarmak için IGNORE vardır: o ifade boş olmasa da satır, diğer ifadeler boşsa atılır. Bu uygulamanın motoru IGNORE'u desteklemez; söz dizimi aşağıda:
EVALUATE
SUMMARIZECOLUMNS(
Urunler[Urun], Tarih[Yil],
"S", [Net Satış],
"K", IGNORE([Kayıtlı Müşteri])
)
Ek bilgi: Eski kaynaklarda "SUMMARIZECOLUMNS'u ölçü içinde kullanma" uyarısını görürsün. Microsoft Learn'e göre Haziran 2024'ten bu yana SUMMARIZECOLUMNS her bağlam geçişinde çalışabiliyor ve ölçü içinde tam destekleniyor. Yine de sonuç sırası garanti edilmez; sıra gerekiyorsa sorguda ORDER BY kullan.
GENERATE ve CROSSJOIN: kombinasyon üretmek
Pazarlama her kanalın en iyi ayını görmek istiyor:
EVALUATE
ADDCOLUMNS(
GENERATE(
VALUES(Satislar[Kanal]),
TOPN(1, CALCULATETABLE(VALUES(Tarih[YilAy])), [Net Satış])
),
"Satış", [Net Satış]
)
Sonuç: Web 2024-12 (44.653,15 ₺), Mobil 2024-06 (41.208,05 ₺), Mağaza 2024-12 (17.423,45 ₺). Mekanizma: GENERATE ilk tablonun her satırı için ikinci ifadeyi o satırın satır bağlamında hesaplar. TOPN içindeki [Net Satış] bağlam geçişiyle hem dış satırı (Kanal) hem iç satırı (YilAy) filtreye çevirir. CALCULATETABLE, ay listesini kanala göre süzer; o kanalda satış olmayan aylar listeye girmez.
- GENERATE, ikinci ifade boş tablo döndürürse o satırı atar (iç birleştirme gibi). Boş satırları tutmak için Power BI'da GENERATEALL vardır; bu uygulamada desteklenmez.
- İki tablonun sütun adları çakışırsa hata alırsın; SELECTCOLUMNS ile yeniden adlandır.
- CROSSJOIN maliyetini önceden hesapla: Musteriler × Urunler × Tarih = 150 × 40 × 1.096 = 6.576.000 satır. Bir ölçünün içinde bu boyutta tablo kurmak belleği ve süreyi patlatır.
Veri soyu nerede kaybolur?
Soy bağını ilk bölümde gördün. Üretimde soru şu: soy hangi adımda düşer? Soysuz bir tablo CALCULATE'e filtre olarak verilince Power BI'da ya hata alırsın ya da tablo hiçbir model sütununu süzmez. İki durumda da beklediğin sonuç gelmez.
| Adım | Soy |
|---|---|
VALUES(Tarih[Yil]), FILTER(VALUES(...), ...), CALCULATETABLE(...) | Korunur |
ADDCOLUMNS(..., "Yeni", ifade) ile eklenen sütun | Yeni sütunun soyu yok |
SELECTCOLUMNS(..., "Y", Tarih[Yil] - 1) gibi bir ifade | Kaybolur |
{ 2023, 2024 }, DATATABLE, GENERATESERIES | Hiç yok |
Örnek: "Bir önceki yıllar" listesini elle üretip satışı süzmek istiyorsun. Listeyi SELECTCOLUMNS ile kurduğun anda soy düşer. TREATAS soyu yeniden verir:
-- Soy yok: filtre beklendiği gibi çalışmaz Geçen Yıl (Hatalı) = CALCULATE([Net Satış], SELECTCOLUMNS(VALUES(Tarih[Yil]), "Y", Tarih[Yil] - 1))
Geçen Yıl (TREATAS) =
CALCULATE(
[Net Satış],
TREATAS(SELECTCOLUMNS(VALUES(Tarih[Yil]), "Y", Tarih[Yil] - 1), Tarih[Yil])
)
2024 satırında 400.869,55 ₺ (2023'ün satışı) döner. Power BI'da SELECTCOLUMNS içindeki sade bir sütun başvurusu ("Y", Tarih[Yil]) soyu korur; ifade yazdığın anda korumaz. Bu uygulamanın motoru da aynı kuralı izler: sade başvuruda soy korunur, ifadede kopar. Emin olamadığında TREATAS ile soyu açıkça ver.
Tablo değişkenleri: bir kez hesaplanır, sabit kalır
Sabit Liste = VAR T = VALUES(Urunler[Urun]) RETURN CALCULATE(COUNTROWS(T), Kategoriler[Kategori] = "Kitap")
Sonuç 7 değil 40'tır. VAR, tanımlandığı yerde ve o andaki filtre bağlamında bir kez hesaplanır. Sonradan gelen CALCULATE değişkenin içeriğini değiştiremez; yalnızca yeni ifadeleri etkiler. Kitap filtresinin listeye uygulanması için tablo ifadesi CALCULATE'in içinde yazılmalı: CALCULATE(COUNTROWS(VALUES(Urunler[Urun])), Kategoriler[Kategori] = "Kitap") 7 döndürür.
- Bu davranış performans için iyidir: aynı tabloyu üç kez kullanacaksan bir kez hesaplanır.
- Değişken tablonun eklenen sütunlarına
[S]diye başvurursun;T[S]yazılamaz. Modelden gelen sütunlar özgün adlarıyla (Urunler[Urun]) kullanılır. - Değişken tablo soyunu korur; yani CALCULATE'e filtre olarak verilebilir.
Küme işlemlerinin kuralları
2023 ve 2024'te alışveriş yapan müşteri kümeleri (iptaller hariç) 77 ve 123 kişidir:
| İşlem | Sonuç | İş dili |
|---|---|---|
INTERSECT(M2023, M2024) | 64 | İki yıl da alan |
EXCEPT(M2023, M2024) | 13 | 2024'te gelmeyen |
EXCEPT(M2024, M2023) | 59 | 2024'te 2023'te olmayan |
UNION(M2023, M2024) | 200 | Tekrarlar kalır |
DISTINCT(UNION(...)) | 136 | Herhangi bir yılda alan |
- EXCEPT simetrik değildir; sıra yanlışsa "kayıp" yerine "yeni" sayarsın.
- Sütunlar adla değil konumla eşleşir; sütun sayısı aynı olmalıdır. Sonucun sütun adları ve soyu ilk tablodan gelir.
- Sonuç kümesi filtre olarak kullanılabilir:
CALCULATE([Net Satış], EXCEPT(M2024, M2023), Tarih[Yil] = 2024)bu 59 kişinin 2024 satışını (315.901,35 ₺) verir.
Bu kümelerle tam bir müşteri köprüsü kurmayı İleri iş kalıpları dersinde, dönem tanımlarını Müşteri analitiği dersinde bulursun.
Kontrol listesi
- "Var olan" mı "olası" kombinasyon mu istiyorsun? SUMMARIZE ile CROSSJOIN'i buna göre seç.
- Sonuçta beklenmedik kadar çok satır var mı? Hiç boş olmayan bir ölçü aranıyor.
- Filtre olarak kullanacağın tablonun soyu hangi adımda düşüyor? Gerekirse TREATAS.
- Tablo değişkeni, sonradan değişmesini beklediğin bir filtreden önce mi tanımlandı?
- EXCEPT'in sırası iş sorusuyla aynı mı?