Büyüyen bir modelde aynı mantık onlarca ölçüde tekrar eder: “geçen yıla göre değişim”, “payda sıfırsa boş döndür”, “şu kanalın payı”. Bir gün tanım değişir (ör. iadeler de düşülecek) ve sen otuz ölçüyü tek tek düzeltirsin; birini unutursun. DAX kullanıcı tanımlı fonksiyonları (user-defined functions, UDF) bu mantığı parametreli bir fonksiyon olarak bir kez yazmanı sağlar.
FUNCTION sözcüğünü desteklemez: UDF kodlarını okuyup anla, alıştırmalarda fonksiyon çağrısının açılmış (inline) hâlini yaz.UDF mi, hesaplama grubu mu, düz ölçü mü?
| Kopyala-yapıştır ölçü | Hesaplama grubu | UDF | |
|---|---|---|---|
| Nasıl uygulanır? | Her ölçüde mantık yeniden yazılır | Görseldeki ölçüye SELECTEDMEASURE() ile otomatik uygulanır | Ölçünün içinde adıyla çağrılır |
| Parametre | Yok | Yok (yalnızca seçili ölçü) | 0–256 parametre, varsayılan değer |
| Kim seçer? | Model yazarı | Rapor kullanıcısı (dilimleyiciyle) | Model yazarı |
| Nerede çalışır? | Ölçü | Ölçüler (örtük ölçüler hariç) | Ölçü, hesaplanmış sütun, görsel hesaplama, başka UDF |
İkisi rakip değil. Hesaplama grubu “kullanıcı Cari / GY / YTD arasında seçsin” sorusunu çözer (Hesaplama grupları, alan parametreleri, görsel hesaplamalar dersi). UDF ise “bu formülü otuz yerde aynı yazalım” sorusunu çözer. Bir hesaplama öğesi de içinde bir UDF çağırabilir.
İlk fonksiyon: tanımla ve EVALUATE ile dene
UDF'yi önce DAX sorgu görünümünde yazıp denemek en güvenli yoldur. DEFINE bloğunda FUNCTION anahtar sözcüğü, adı, parametre listesi ve => işaretinden sonra gövde gelir:
DEFINE
/// Payı paydaya güvenli böler; payda 0 ya da boşsa BLANK döner.
FUNCTION Pbi.Pay = ( pay : NUMERIC, payda : NUMERIC ) =>
DIVIDE ( pay, payda )
EVALUATE
{ Pbi.Pay ( 10, 4 ), Pbi.Pay ( 10, 0 ) }
// 2,5 ve boş
/// ile başlayan satır fonksiyonun açıklamasıdır; IntelliSense bunu gösterir. Düz // ve /* */ yorumlar açıklama olarak görünmez. Sorgu düzgün çalışınca:
- DAX sorgu görünümünde üstteki Modeli değişikliklerle güncelle (Update model with changes) düğmesine ya da editördeki “Update model: Add new function” satırına bas. Fonksiyon modele kaydedilir ve Model gezgininde Functions düğümünde görünür.
- TMDL görünümünde aynı tanım
createOrReplacealtındafunction Pbi.Pay = ...biçimindedir; Uygula ile kaydedilir. PBIP projesinde fonksiyonlardefinitionklasöründekifunctions.tmdldosyasında durur (Yaşam döngüsü: PBIP, TMDL, Git… dersi). - Kaydedilen fonksiyon bir ölçüde çağrılır:
Brüt Marj = Pbi.Pay ( [Brüt Kâr], [Net Satış] ).
İmza tasarlamak: ad, parametre, tür
Fonksiyonun imzası (adı ve parametreleri) bir sözleşmedir; sonradan değiştirmek onu çağıran her ölçüyü etkiler. Learn'deki kurallar:
- Fonksiyon adı modelde benzersiz olmalı; harf, rakam ve alt çizgi içerir. Ad alanı için nokta kullanılabilir (
Pbi.Pay,Finans.Yoy) ama ad noktayla başlayıp bitemez, art arda iki nokta olamaz. Yerleşik bir DAX fonksiyonuyla ya daMEASURE,FUNCTION,DEFINEgibi ayrılmış sözcüklerle çakışamaz. - Parametre adı yalnızca harf, rakam ve alt çizgi içerir; nokta olamaz. Türkçe karakterden kaçınmak (
oran,payda) taşınabilirliği artırır. - Varsayılan değer:
esik : NUMERIC = 0.95parametreyi isteğe bağlı yapar. Varsayılan ifade, fonksiyonun tanımlandığı yerde görünen adlara başvurabilir. - Aşırı yükleme yok: Aynı adla farklı parametreli iki fonksiyon tanımlanamaz. Özyineleme de desteklenmez; dönüş türü açıkça yazılamaz.
| Tür ipucu | Anlamı | Varsayılan mod |
|---|---|---|
AnyVal | Skaler ya da tablo değer (tür yazılmazsa bu) | val |
Scalar + alt tür | Tek değer; alt tür Int64, Decimal, Double, String, DateTime, Boolean, Numeric, Variant | val |
Table | Tablo değer | val |
AnyRef, ColumnRef, TableRef, MeasureRef, CalendarRef | Sütun, tablo, ölçü ya da takvim başvurusu | expr (val yazılamaz) |
Değer türleri örtük tür dönüşümü yapar: x : INT64 parametresine 3,4 verirsen 3 olur. Başvuru türleri dönüşüm yapmaz. Gövdede ISNUMERIC, ISSTRING, ISINT64 gibi denetim fonksiyonlarıyla gelen değeri kontrol edebilirsin.
val ve expr: argüman ne zaman hesaplanır?
Parametrenin geçiş modu UDF'lerin en önemli ve en kolay karıştırılan noktasıdır:
- val (hevesli, eager): Argüman fonksiyon çağrılmadan önce bir kez hesaplanır; fonksiyona yalnızca sonucu gider. Learn'e göre çağıranın hem satır hem filtre bağlamını görür.
- expr (tembel, lazy): Argüman hesaplanmadan, bir ifade olarak gider. Fonksiyon gövdesinde, gövdenin kurduğu bağlamda (ör.
CALCULATEiçinde) ve gerekirse birçok kez hesaplanır. Learn'e göre yalnızca filtre bağlamını devralır.
DEFINE
FUNCTION Pbi.Yoy = ( olcu : SCALAR EXPR ) =>
VAR Simdi = olcu
VAR Once = CALCULATE ( olcu, SAMEPERIODLASTYEAR ( Tarih[Tarih] ) )
RETURN DIVIDE ( Simdi - Once, Once )
FUNCTION Pbi.YoyHatali = ( olcu : SCALAR VAL ) =>
VAR Simdi = olcu
VAR Once = CALCULATE ( olcu, SAMEPERIODLASTYEAR ( Tarih[Tarih] ) )
RETURN DIVIDE ( Simdi - Once, Once )
EVALUATE
SUMMARIZECOLUMNS ( Tarih[Yil],
"expr", Pbi.Yoy ( [Net Satış] ),
"val", Pbi.YoyHatali ( [Net Satış] ) )
val sürümünde olcu artık bir sayıdır; CALCULATE bir sayının filtresini değiştiremez, “geçen yıl” da bu yılla aynı çıkar. Aynı davranışı motorda VAR ile görebilirsin; VAR da val gibi bir kez hesaplanır:
EVALUATE
SUMMARIZECOLUMNS(Tarih[Yil],
"expr gibi", DIVIDE([Net Satış] - CALCULATE([Net Satış], SAMEPERIODLASTYEAR(Tarih[Tarih])),
CALCULATE([Net Satış], SAMEPERIODLASTYEAR(Tarih[Tarih]))),
"val gibi", VAR S = [Net Satış]
RETURN DIVIDE(S - CALCULATE(S, SAMEPERIODLASTYEAR(Tarih[Tarih])),
CALCULATE(S, SAMEPERIODLASTYEAR(Tarih[Tarih]))))
Sonuç: expr gibi 2023'te %289,8, 2024'te %86,4; val gibi her yıl 0. Kural basit: Fonksiyonun içinde argümanın bağlamını değiştireceksen (CALCULATE, yineleyici, zaman zekâsı) parametre expr olmalı. Yalnızca gelen sayıyla aritmetik yapacaksan (Pbi.Pay gibi) val yeterli ve daha ucuzdur; argüman bir kez hesaplanır.
Yineleyicide fark daha da büyür. AVERAGEX(VALUES(Tarih[YilAy]), olcu) gövdeli bir fonksiyonda expr parametre her ay için ayrı hesaplanır: filtresiz durumda satış olan 31 ayın ortalaması 40.347 TL'dir. Aynı parametre val olsaydı tek bir toplam (1.250.772) her aya tekrar yazılır ve “ortalama” toplamın kendisine eşit çıkardı.
Test etmek
- Bilinen değerle karşılaştır:
EVALUATE { Pbi.Pay ( 10, 4 ) }gibi elle hesaplanabilen girdilerle başla. - Hazır ölçüyle aynı mı? Fonksiyonla yazdığın ölçüyü eski ölçünün yanına bir tabloya koy:
SUMMARIZECOLUMNS ( Tarih[Yil], "Eski", [YoY %], "Yeni", Pbi.Yoy ( [Net Satış] ) ). Her satır aynı olmalı. - Kenar durumları: payda 0, BLANK, negatif değer, geçen yılı olmayan ilk yıl (2022).
- Modeldeki fonksiyonları listele:
EVALUATE INFO.USERDEFINEDFUNCTIONS ()modeldeki UDF'leri ve ifadelerini döndürür; belgelendirme ve gözden geçirme için kullanışlıdır.
Sınırlar (Learn'e göre)
- UDF'ler görüntüleme klasörlerine konamaz, çevirilerle birleştirilemez; tablosu olmayan modellerde desteklenmez.
- Nesne düzeyi güvenlik fonksiyonu korumaz: Gizli bir sütunu kullanan fonksiyon otomatik gizlenmez. Gizli nesnelerin adını fonksiyon adına ya da açıklamasına yazma.
- Canlı bağlantılı bir raporda kaynak modelin fonksiyonları Model gezgininde görünmez; bileşik modeldeki ölçüler kaynak modelin UDF'lerine başvuramaz.
- Görsel hesaplama formül çubuğunda IntelliSense yoktur; sütunu expr parametre olarak geçirmek ya da tablo adı olmadan sütun yazmak kırmızı alt çizgi ve doğrulama hatası üretebilir. Tablo adıyla nitelenmemiş adlar ölçü başvurusu sayılır.
Finans., Zaman.), her birine /// açıklaması ve @param satırları yaz, bir test sorgusunu PBIP deposunda sakla. Fonksiyonu değiştirince test sorgusunu yeniden çalıştır.