21 Eki 2026 · @atilayberber
Haziran 2026 itibarıyla DAX’ta kendi fonksiyonlarınızı yazabiliyorsunuz. Kullanıcı Tanımlı Fonksiyonlar (User-Defined Functions, UDF) artık Power BI Desktop’ta ve Power BI Service’te genel kullanımda ve varsayılan olarak açık. Bu yazıda UDF’lerin ne olduğunu, sözdizimini, parametre türlerini, perakendeden gerçek örnekleri, hesaplama gruplarıyla farkını ve üretimde dikkat edilmesi gerekenleri adım adım anlatıyorum.
Giriş: kopyala-yapıştır DAX’ın gizli maliyeti
Büyüyen her Power BI modelinde aynı hikaye yaşanır. Brüt kâr marjı formülü önce bir ölçüde yazılır. Sonra mağaza, kategori ve bölge raporları için üç kopyası daha oluşur. Birinde DIVIDE yerine / kullanılır, diğerinde başka bir maliyet sütunu seçilir. Altı ay sonra aynı KPI’ın üç farklı tanımı vardır ve kimse hangisinin doğru olduğunu bilmez.
Bu sorunun adı KPI kaymasıdır (KPI drift). DAX’ta bugüne kadar gerçek bir çözümü yoktu. Hesaplama grupları ve ara ölçüler (base measure) işe yarıyordu ama parametre alamadıkları için her duruma uymuyordu.
UDF’ler bu boşluğu doldurur. Bir iş kuralını bir kez, parametreli bir fonksiyon olarak tanımlarsınız. Ardından onu ölçülerde, hesaplanan sütunlarda, görsel hesaplamalarda ve başka fonksiyonlarda yerleşik bir DAX fonksiyonu gibi çağırırsınız. Kural değiştiğinde tek bir yeri güncellersiniz. Tabular Editor ekibi bu özelliği son yılların en büyük DAX dil değişikliği olarak nitelendiriyor; bence de abartı değil.
Önizlemeden genel kullanıma: kısa tarihçe
UDF’ler Eylül 2025 Power BI Desktop güncellemesiyle önizlemeye açıldı ve Haziran 2026 sürümüyle genel kullanıma (GA) geçti. Microsoft duyuruyu 2 Haziran 2026’da yaptı; 9 Haziran’da çıkan Desktop sürümünde (2.155.756.0) özellik artık ayar menüsünden açılması gerekmeden varsayılan olarak geliyor.
| Dönem | Ne oldu |
|---|---|
| Haziran 2026 | GA: isteğe bağlı parametreler, web’de modelleme, Model görünümünde düzenleme, varsayılan olarak açık |
| Haziran 2026 | Tabular Editor 3.26.2: GA sözdizimi ve tip uyuşmazlığı uyarıları |
| Aralık 2025 | SQLBI, topluluk fonksiyon kütüphanesi DAX Lib’i duyurdu |
| Eylül 2025 | Power BI Desktop’ta genel önizleme |
GA ile önizlemeye göre gelen en önemli yenilikler şunlar:
- İsteğe bağlı parametreler: Bir parametreye
= <varsayılan ifade>vererek onu atlanabilir yapabilirsiniz. - Web’de modelleme: UDF’leri Power BI Service’te, tarayıcıdan görüntüleyip düzenleyebilirsiniz.
- Model görünümü: Desktop’ta sağ tık › Yeni fonksiyon ile oluşturup formül çubuğunda düzenleyebilirsiniz.
- Bağımlılık takibi: Bir tabloyu, sütunu veya ölçüyü yeniden adlandırdığınızda modeldeki fonksiyon ifadeleri otomatik güncellenir.
- Katalog:
INFO.USERDEFINEDFUNCTIONS()modeldeki tüm UDF’leri ayrıntılarıyla listeler. - SSMS desteği: SQL Server Management Studio 22.5 ve sonrasında UDF’leri betikleyip oluşturabilirsiniz (sözdizimi renklendirmesi henüz yok).
Özellik veritabanı uyumluluk düzeyi 1702 veya üzeri gerektirir. Azure Analysis Services tarafında da Microsoft çalışanları topluluk forumunda UDF’lerin artık kullanılabilir olduğunu belirtiyor; kurumsal bir AAS ortamında kullanmadan önce kendi sunucunuzda doğrulamanızı öneririm.
UDF nedir? Temel sözdizimi
UDF, parametre alan ve sonuç döndüren, modelde adıyla saklanan bir DAX ifadesidir. Yeni FUNCTION anahtar kelimesiyle tanımlanır ve => okunun sağındaki gövde, bildiğiniz DAX’tan ibarettir. Genel kalıp şöyle:
DEFINE
/// Fonksiyonun açıklaması
/// @param {Tür} ParametreAdı - Parametre açıklaması
/// @returns Dönüş değerinin açıklaması
FUNCTION <FonksiyonAdı> =
( <parametre> [: <tür> <alt tür> <mod>] [= <varsayılan>], ... ) =>
<gövde>
İlk fonksiyonunuz: KDV ekleme
DAX sorgu görünümünü (DAX query view) açın ve aşağıdaki sorguyu çalıştırın:
DEFINE
/// Tutara KDV ekler. Oran verilmezse %20 kullanılır.
/// @param {NUMERIC} tutar - KDV hariç tutar
/// @param {NUMERIC} [oran] - İsteğe bağlı KDV oranı, varsayılan 0.20
/// @returns KDV dahil tutar
FUNCTION KdvEkle =
( tutar : NUMERIC, oran : NUMERIC = 0.20 ) =>
tutar * ( 1 + oran )
EVALUATE
{ KdvEkle ( 100 ), KdvEkle ( 100, 0.10 ), KdvEkle ( 100, 0.01 ) }
// 120, 110, 101
Bu haliyle fonksiyon yalnızca o sorgu için vardır. Modele kaydetmek için üstteki Modeli değişikliklerle güncelle (Update model with changes) düğmesini ya da fonksiyonun hemen üstünde beliren Update model: Add new function bağlantısını kullanın. Kaydedilen fonksiyon Model gezgininde (Model explorer) Functions düğümünde görünür.
Artık herhangi bir ölçüde yerleşik fonksiyon gibi çağırabilirsiniz:
Ciro KDV Dahil = KdvEkle ( [Ciro] )
Ciro KDV Dahil (Temel Gıda) = KdvEkle ( [Ciro], 0.01 )
Hesaplanan sütunda kullanırken dönüş türünün tutarlı olmasına dikkat edin; gerekirse sonucu CONVERT ile sabitleyin:
Satır Tutarı KDV Dahil = CONVERT ( KdvEkle ( 'Satış'[Tutar] ), CURRENCY )
/// yorumları neden önemli?
Üç eğik çizgiyle (///) başlayan satırlar fonksiyonun belgesidir. Fonksiyon adını yazdığınızda IntelliSense bu açıklamayı ve imzayı gösterir. // veya /* */ yorumları ise IntelliSense’te görünmez. Açıklamalar INFO.USERDEFINEDFUNCTIONS() çıktısına da yansır; yani iyi yazılmış yorumlar modelinizin yaşayan kataloğu olur. Copilot gibi yapay zekâ araçları da tipli imza ve açıklamalar sayesinde iş mantığınızı tahmin etmek yerine doğrudan bu fonksiyonları bulup çağırabiliyor.
İsimlendirme kuralları
- Fonksiyon adı modelde benzersiz olmalı ve yerleşik bir DAX fonksiyonu ya da ayrılmış kelimeyle (
MEASURE,FUNCTION,DEFINE) çakışmamalı. - Harf, rakam ve alt çizgi kullanılabilir; boşluk ve özel karakter kullanılamaz. Türkçe karakterlerden (ş, ğ, ı, ç, ö, ü) kaçınmak en güvenlisi.
- Nokta ile isim alanı oluşturabilirsiniz:
Satis.GecenYilaGore,Finans.BrutMarj. Ad nokta ile başlayıp bitemez, iki nokta yan yana gelemez. - Parametre adlarında nokta kullanılamaz.
- SQLBI, yerleşik BÜYÜK HARF fonksiyonlardan ayırt edilsin diye UDF’lerde PascalCase öneriyor.
Fonksiyonlar nerede yazılır ve yönetilir?
UDF’ler modelin birinci sınıf nesneleridir; tablolar ve ölçüler gibi pek çok araçtan erişilebilirler. Hangisini seçeceğiniz çalışma tarzınıza bağlı.
| Araç | En uygun olduğu iş | Not |
|---|---|---|
| DAX sorgu görünümü (Desktop) | Yazmak, test etmek, hızlı deneme | Model gezgininde sağ tık › Quick queries: Evaluate, Define and evaluate, Define all functions in this model |
| TMDL görünümü (Desktop) | Kod odaklı toplu düzenleme | createOrReplace betikleri; üstteki Apply ile modele yazılır. Sağ tık › Script TMDL to |
| Model görünümü (Desktop) | Tek fonksiyonu hızlıca oluşturmak | Functions › Yeni fonksiyon, formül çubuğunda düzenleme |
| Web’de modelleme (Service) | Desktop’sız düzenleme, ekip içi iş birliği | GA ile geldi |
| Power BI Project (PBIP) + Git | Sürüm kontrolü, kod incelemesi | Fonksiyonlar definition/functions.tmdl dosyasında tutulur |
| Tabular Editor 3 | Gelişmiş yazar deneyimi, paket yönetimi | 3.23.0’dan beri destekliyor; isteğe bağlı parametre sözdizimi için 3.26.2+ |
| SSMS 22.5+ | Betikleme, DBA iş akışları | Sözdizimi renklendirmesi ve IntelliSense yok |
| XMLA uç noktası / Semantic Link Labs | Onlarca modele otomatik dağıtım | Fabric not defterlerinden betiklenebilir |
Aynı KDV fonksiyonunun TMDL karşılığı şöyle görünür:
createOrReplace
/// Tutara KDV ekler. Oran verilmezse %20 kullanılır.
function KdvEkle =
( tutar : NUMERIC, oran : NUMERIC = 0.20 ) =>
tutar * ( 1 + oran )
Modeldeki fonksiyonları sorgulamak için iki DMV fonksiyonu var:
// Tam meta veri (yazma yetkisi gerektirir)
EVALUATE INFO.USERDEFINEDFUNCTIONS ()
// Yalnızca adlar ve sınırlı bilgi
EVALUATE INFO.FUNCTIONS ( "ORIGIN", "2" )
Küçük ama önemli bir ayrıntı: DEFINE FUNCTION ile yazıp modele kaydetmediğiniz fonksiyonlar INFO.USERDEFINEDFUNCTIONS() sonucunda görünmez. Bunlar sorgu kapsamlıdır ve test için idealdir.
Parametreler: UDF’lerin asıl gücü ve asıl tuzağı
Bir UDF 0 ile 256 arası parametre alabilir. Her parametreye isteğe bağlı üç ipucu verilebilir: tür, alt tür ve geçiş modu. Yazım sırası ad : [tür] [alt tür] [mod] şeklindedir. Hiçbir ipucu vermezseniz parametre AnyVal val gibi davranır.
Türler: değer mi, referans mı?
| Tür | Ne kabul eder | Mod |
|---|---|---|
AnyVal | Skaler veya tablo (varsayılan) | val (expr verilebilir) |
Scalar | Tek bir değer; alt türle daraltılabilir | val (expr verilebilir) |
Table | Bir tablo | val (expr verilebilir) |
AnyRef | Herhangi bir model nesnesi referansı | yalnızca expr |
MeasureRef | Bir ölçü referansı | yalnızca expr |
ColumnRef | Bir sütun referansı | yalnızca expr |
TableRef | Bir tablo referansı | yalnızca expr |
CalendarRef | Bir takvim referansı | yalnızca expr |
Değer türleri (AnyVal, Scalar, Table) örtük tür dönüşümü yapar; referans türleri yapmaz. Scalar için alt türler: Variant, Int64, Decimal, Double, String, DateTime, Boolean ve bunların sayısal olanlarını kapsayan Numeric. Alt tür yazdığınızda Scalar demenize gerek kalmaz.
Tür ipucu sessiz dönüşüm yapar; bunu bilmek sürprizleri önler:
DEFINE
FUNCTION TamSayiyaCevir = ( x : INT64 ) => x
EVALUATE
{ TamSayiyaCevir ( 3.4 ), TamSayiyaCevir ( 3.5 ), TamSayiyaCevir ( "5" ) }
// 3, 4, 5 → ondalık yuvarlanır, metin sayıya döner
VAL ve EXPR: en kritik kavram
VAL (erken değerlendirme), argümanı fonksiyona girmeden önce çağıranın bağlamında bir kez hesaplar ve fonksiyona hazır sonucu verir. EXPR (tembel değerlendirme) ise ifadeyi hesaplamadan aktarır. Fonksiyon onu kendi içinde, gerekirse farklı bir filtre bağlamında ve birden fazla kez değerlendirir.
Farkı görmenin en iyi yolu aynı gövdeyi iki modla yazmak:
DEFINE
/// Hazır tablo alır; içerideki ALL hiçbir şey değiştiremez
FUNCTION SatirSay_Val = ( t : TABLE VAL ) =>
COUNTROWS ( CALCULATETABLE ( t, ALL ( 'Tarih' ) ) )
/// İfadeyi alır; ALL dışarıdan gelen yıl filtresini kaldırır
FUNCTION SatirSay_Expr = ( t : TABLE EXPR ) =>
COUNTROWS ( CALCULATETABLE ( t, ALL ( 'Tarih' ) ) )
EVALUATE
{
CALCULATE ( SatirSay_Val ( 'Satış' ), 'Tarih'[Yıl] = 2025 ), // yalnızca 2025
CALCULATE ( SatirSay_Expr ( 'Satış' ), 'Tarih'[Yıl] = 2025 ) // tüm yıllar
}
İlk fonksiyonda 'Satış' tablosu 2025 filtresiyle zaten hesaplanmış olarak gelir. İçerideki ALL('Tarih') artık etkisizdir. İkincisinde fonksiyon ifadeyi kendi bağlamında çalıştırır ve yıl filtresi kalkar. Bir ayrıntı daha: VAL parametre çağıranın hem satır hem filtre bağlamını miras alır; EXPR parametre fonksiyon içinde değerlendiği için yalnızca filtre bağlamını alır.
Pratik kural:
- Fonksiyon içinde
CALCULATE, zaman zekâsı ya da iterasyonla bağlamı değiştirecekseniz EXPR veya MeasureRef kullanın. - Sabit bir oran, eşik ya da tek seferlik bir değer yetiyorsa VAL kullanın.
- Modu her zaman açıkça yazın. SQLBI’ın önerisiyle EXPR parametrelerini adından tanınacak şekilde adlandırın:
metrikExpr,tutarExpr. - EXPR parametresini fonksiyon içinde
CALCULATE ( ... )ile sarın. Böylece çağıran ister bir ölçü ister ham birSUMXifadesi göndersin, satır bağlamı doğru şekilde filtre bağlamına dönüşür. SQLBI’ın örneğinde bu unutulduğunda fonksiyon boş tablo döndürüyor.
İsteğe bağlı parametreler (GA ile geldi)
Parametreye = <ifade> eklediğinizde parametre atlanabilir olur:
FUNCTION Topla = ( x : NUMERIC = 1, y : NUMERIC = 2 ) => x + y
// Geçerli çağrılar: Topla (), Topla ( 5 ), Topla ( , 10 ), Topla ( 5, 10 )
Bilinmesi gereken dört kural var:
- Konum serbesttir. İsteğe bağlı parametre zorunlu olanlardan önce de gelebilir; argüman boş bırakılarak atlanır (
F ( 1, , 3 )). Ancak en az argüman sayısını en sağdaki zorunlu parametre belirler. - Kapsam tanım yerindedir. Varsayılan ifade, çağrıldığı yerdeki değil, fonksiyonun tanımlandığı yerdeki adları görür. Başka bir isteğe bağlı parametreye başvuramaz.
- Bağlamı mod belirler. Varsayılan ifadenin hangi bağlamı miras alacağı parametrenin VAL ya da EXPR olmasına bağlıdır.
- Tip kontrolü koşulludur. Tür ipucu yalnızca varsayılan kullanıldığında varsayılan ifadeye, argüman verildiğinde ise o argümana uygulanır.
Çalışma zamanı tip kontrolü ve yardımcı fonksiyonlar
Bir parametrenin gerçekte hangi türde geldiğini gövde içinde ISNUMERIC, ISINT64, ISDECIMAL, ISDOUBLE, ISSTRING, ISBOOLEAN, ISDATETIME gibi fonksiyonlarla sınayabilirsiniz. Böylece aynı fonksiyon hem bir para birimi anahtarını (36) hem kodunu ("USD") kabul edebilir. Referans parametrelerle çalışırken iki yeni bilgi fonksiyonu da iş görür: TABLEOF bir sütun, ölçü veya takvimin ait olduğu tabloyu, NAMEOF ise nesnenin adını metin olarak döndürür.
Perakendeden dört gerçek örnek
Aşağıdaki örnekler tipik bir market zinciri modeli varsayıyor: 'Satış' olgu tablosu, 'Tarih' takvim tablosu ve [Ciro], [Maliyet] gibi temel ölçüler. Hepsini önce bir DAX sorgusunda test edip sonra modele kaydedebilirsiniz.
1. Brüt marj ve performans bandı: fonksiyon içinde fonksiyon
Bir UDF başka bir UDF’yi çağırabilir. Marj hesabı tek yerde kalır, sınıflandırma onun üzerine kurulur:
DEFINE
/// Brüt kârı ciroya oranlar. Ciro sıfırsa BLANK döner.
FUNCTION Finans.BrutMarj =
( ciro : DECIMAL, maliyet : DECIMAL ) =>
DIVIDE ( ciro - maliyet, ciro )
/// Marjı banda ayırır: >= %40 Güçlü, >= %20 Orta, altı Zayıf.
FUNCTION Finans.MarjBandi =
( ciro : DECIMAL, maliyet : DECIMAL ) =>
VAR m = Finans.BrutMarj ( ciro, maliyet )
RETURN
SWITCH ( TRUE (),
ISBLANK ( m ), BLANK (),
m >= 0.40, "Güçlü",
m >= 0.20, "Orta",
"Zayıf" )
EVALUATE
{ Finans.MarjBandi ( 250000, 175000 ) } // "Orta" (%30)
Ölçülerde kullanımı:
Brüt Marj % = Finans.BrutMarj ( [Ciro], [Maliyet] )
Marj Bandı = Finans.MarjBandi ( [Ciro], [Maliyet] )
2. Geçen yıla göre değişim: herhangi bir ölçü için
Klasik yöntemde Ciro YoY, Fiş Sayısı YoY, Sepet Ortalaması YoY için ayrı ayrı aynı kalıbı yazarsınız. MEASUREREF ve COLUMNREF ile tek fonksiyon yeter:
DEFINE
/// Bir ölçünün geçen yılın aynı dönemine göre değişimi.
/// @param {MEASUREREF} olcu - Karşılaştırılacak ölçü
/// @param {COLUMNREF} tarih - Takvim tablosunun tarih sütunu
/// @param {BOOLEAN} [yuzde] - TRUE: yüzde, FALSE: fark. Varsayılan TRUE
FUNCTION Satis.GecenYilaGore =
(
olcu : MEASUREREF,
tarih : COLUMNREF,
yuzde : SCALAR BOOLEAN VAL = TRUE
) =>
VAR Bu = olcu
VAR Onceki = CALCULATE ( olcu, SAMEPERIODLASTYEAR ( tarih ) )
RETURN
IF ( yuzde, DIVIDE ( Bu - Onceki, Onceki ), Bu - Onceki )
Ciro YoY % = Satis.GecenYilaGore ( [Ciro], 'Tarih'[Tarih] )
Fiş Sayısı YoY % = Satis.GecenYilaGore ( [Fiş Sayısı], 'Tarih'[Tarih] )
Ciro YoY Fark (₺) = Satis.GecenYilaGore ( [Ciro], 'Tarih'[Tarih], FALSE )
Ölçü referansı (MEASUREREF) her kullanıldığı bağlamda yeniden hesaplanır; CALCULATE içindeki tarih filtresi bu yüzden çalışır. Ölçünün değeri VAL bir parametreyle gönderilseydi Onceki de bu yılın değerine eşit çıkardı.
3. Hareketli ortalama: pencere boyu parametreli
Günlük ciro dalgalıdır; trendi görmek için 7 veya 28 günlük ortalama kullanılır. Pencere boyunu parametre yapıp varsayılanı 7 gün bırakalım:
DEFINE
/// Ölçünün son N günlük basit hareketli ortalaması. Varsayılan 7 gün.
FUNCTION Satis.HareketliOrtalama =
(
olcu : MEASUREREF,
tarih : COLUMNREF,
gun : SCALAR INT64 VAL = 7
) =>
VAR Pencere =
DATESINPERIOD ( tarih, MAX ( tarih ), - gun, DAY )
RETURN
AVERAGEX ( Pencere, olcu )
Ciro 7G Ort = Satis.HareketliOrtalama ( [Ciro], 'Tarih'[Tarih] )
Ciro 28G Ort = Satis.HareketliOrtalama ( [Ciro], 'Tarih'[Tarih], 28 )
AVERAGEX her tarih satırında ölçüyü değerlendirir; ölçü referansı örtük bir CALCULATE taşıdığı için bağlam geçişi kendiliğinden olur.
4. Rakip fiyat endeksi: aynı kural hem ölçüde hem sütunda
Rakip fiyatı izleyen ekipler için fiyat farkı yüzdesi hem ürün bazlı bir sütunda hem toplam düzeyde bir ölçüde gerekir. UDF iki yerde de aynı kuralı garanti eder:
/// Bizim fiyatın rakibe göre yüzde farkı. Pozitif: biz daha pahalıyız.
FUNCTION Fiyat.RakipFarki =
( bizimFiyat : DOUBLE, rakipFiyat : DOUBLE ) =>
DIVIDE ( bizimFiyat - rakipFiyat, rakipFiyat )
// Hesaplanan sütun (ürün satırı bazında)
Rakip Fark % = Fiyat.RakipFarki ( 'Ürün Fiyat'[Bizim Fiyat], 'Ürün Fiyat'[Rakip Fiyat] )
// Ölçü (seçili ürünlerin ortalaması üzerinden)
Ort. Rakip Fark % =
Fiyat.RakipFarki (
AVERAGE ( 'Ürün Fiyat'[Bizim Fiyat] ),
AVERAGE ( 'Ürün Fiyat'[Rakip Fiyat] )
)
Tek modelden kurumsal kütüphaneye
UDF’lerin asıl getirisi, tek bir raporu değil tüm anlamsal katmanı (semantic layer) tutarlı hale getirmesidir. Bunun için fonksiyonları kod gibi yönetmek gerekir.
İsim alanlarıyla düzen
Microsoft, fonksiyon adlarını ekip veya alan önekiyle başlatmayı öneriyor: Finans.Marj, Tedarik.TeminSuresi, IK.Ayrilma. Perakendede işe yarayan bir şema:
| Önek | İçerik | Örnek |
|---|---|---|
Finans. | Marj, kârlılık, KDV, kur | Finans.BrutMarj |
Satis. | Zaman karşılaştırmaları, sepet, fiş | Satis.GecenYilaGore |
Stok. | Devir hızı, stok günü, fire | Stok.StokGunu |
Fiyat. | Rakip endeksi, indirim derinliği | Fiyat.RakipFarki |
Fmt. | Metin biçimlendirme yardımcıları | Fmt.KisaTutar |
Git ve TMDL ile sürüm kontrolü
Power BI Project (PBIP) formatında fonksiyonlar definition/functions.tmdl dosyasında düz metin olarak durur. Bu dosyayı Git’e koyduğunuzda her değişiklik bir commit, her kural tartışması bir pull request olur. Microsoft’un önerdiği model, Git’e bağlı bir ortak fonksiyon kütüphanesi modeli kurmak. Diğer model geliştiriciler onaylanmış fonksiyonları buradan alır; böylece tek kaynak, sıfır kayma hedeflenir. Çok sayıda modele dağıtım için XMLA uç noktası veya Fabric not defterlerinde Semantic Link Labs ile betik yazılabilir.
DAX Lib: fonksiyonların uygulama mağazası
SQLBI, Aralık 2025’te DAX Lib adlı topluluk kütüphanesini duyurdu. Duyuru sırasında sitede SVG görselleri, ölçü biçimlendirme, birim dönüşümleri ve DAX kalıpları gibi konularda 30’dan fazla kütüphane vardı ve sayı büyümeye devam ediyor. Kullanmanın iki yolu var:
- Power BI Desktop: Kütüphanenin TMDL betiğini kopyalayın, modelin TMDL görünümüne yapıştırın ve Apply’a basın.
- Tabular Editor 3: Yerleşik DAX Package Manager ile paketleri tarayıcı açmadan kurun, güncelleyin veya eski sürüme dönün. Kur düğmesi yalnızca uyumluluk düzeyi 1702 ve üzeri modellerde çıkar. Paketler nokta ile ayrılmış adlar kullanır; örneğin
TimeSeries.MovingAveragepaketiTimeSeries.MovingAverage.Simplefonksiyonunu getirir.
Kurum içi (özel) kütüphane desteği Tabular Editor tarafında planlanmış ama henüz yok. O zamana kadar ortak kütüphane modeli + Git en sağlam yol.
Yönetişim: fonksiyon bir KPI tanımıdır
Bir kural fonksiyona taşındığında artık tek bir ölçünün değil, onu çağıran tüm ölçülerin davranışını belirler. Hatalı bir tanım da aynı kolaylıkla her yere yayılır. Bu yüzden her fonksiyon için şunları kayıt altına alın: ad, girdiler, iş kuralının sade Türkçe tanımı, sahibi ve son doğrulama tarihi. Fonksiyona çevirmeden önce kuralın kendisinde, örneğin finansla mutabakat sağlayın; sözdizimi kolay kısımdır.
UDF mi, hesaplama grubu mu?
Kısa cevap: UDF geliştirici içindir, hesaplama grubu son kullanıcı için. İkisi rakip değil, farklı sorunları çözer. SQLBI’ın karşılaştırmasını özetlersek:
| Ölçüt | UDF | Hesaplama grubu |
|---|---|---|
| Parametre | Açık, tipli parametreler | Yok; ayar filtre bağlamıyla gelir |
| Kim görür | Raporda görünmez, geliştirici kullanır | Dilimleyicide, alan listesinde, filtrelerde görünür |
| Uygulama | Her ölçüde farklı argümanla | Bir görseldeki tüm ölçülere aynı dönüşüm |
| Çalışma biçimi | Çağrıldığı yerde makro gibi açılır | Sorgu anında seçili ölçüleri dönüştürür |
| Ek yük | Parametreler yürütmeden önce çözülür | Sorgu anında ek filtre işlemi |
UDF seçin: Aynı mantık farklı argümanlarla birçok yerde gerekiyorsa, mantık kopyalanamayacak kadar karmaşıksa, aynı görseldeki ölçüler kendi parametrelerini taşımalıysa.
Hesaplama grubu seçin: Kur dönüşümü, dönem karşılaştırması gibi bir dönüşümün görseldeki tüm ölçülere uygulanması gerekiyorsa ve kullanıcının bunu dilimleyiciyle seçmesi isteniyorsa (örneğin Gerçekleşen / Bütçe).
Zaman zekâsında dengeler değişiyor
Tabular Editor ekibinin 2026 değerlendirmesi, zaman zekâsı için UDF tabanlı açık ölçülerin pek çok durumda hesaplama gruplarının önüne geçtiğini gösteriyor:
- Hesaplama öğeleri başlıkları, araç ipuçlarını, filtre panelindeki ölçü filtrelerini ve koşullu biçimlendirmeyi de etkiler. UDF yalnızca onu açıkça çağıran ölçüyü etkiler.
- Matris düzeninde ölçüler serbestçe gruplanıp sıralanabilir; ölçü bazlı koşullu biçimlendirme ikiz ölçü hilesi gerektirmez.
- Performans tarafında yazar, UDF ölçülerinin pratikte en az hesaplama grupları kadar iyi çalıştığını aktarıyor; basit filtrelerde hesaplama gruplarının DAX fusion optimizasyonunu bozduğu bir demoya da atıfta bulunuyor.
Bedeli: Her hesaplama için ayrı ölçü gerekir (C# betikleriyle üretilebilir) ve dilimleyiciyle değiştirme için alan parametreleri gerekir. Rol oynayan boyutlar ve TREATAS tabanlı ilişkiler gibi senaryolarda hesaplama grupları hâlâ daha iyi. En güçlü tasarım çoğu zaman ikisinin birlikteliği: mantık UDF’de yaşar, hesaplama öğesi o UDF’yi çağırır.
Kaputun altı: performans ve hata ayıklama
UDF’ler bir çağrı yığınında çalışan alt programlar değildir. SQLBI’ın DAX iç yapısı belgelerine göre fonksiyon gövdesi, çağrıldığı yere makro gibi açılır (inline expansion). Bunun üç pratik sonucu var:
- Fonksiyonun kendisi ek yük getirmez. Motor sonuçta açılmış DAX’ı çalıştırır; iyi yazılmış bir UDF’nin elle yazılmış eşdeğeriyle aynı performansı göstermesi beklenir.
- Kötü yazılmış bir UDF her yerde kötüdür. Bir fonksiyonu onlarca ölçü çağırır. Gövdedeki tek bir verimsiz iterasyon tüm modele yayılır. Bu yüzden kütüphane fonksiyonları en çok optimize edilmesi gereken kodlardır.
- EXPR parametreleri her başvuruda yeniden hesaplanır. Gövdede aynı EXPR parametresini üç kez kullanırsanız ifade üç kez değerlendirilebilir; bir iterasyonun içindeyse satır başına. Aynı bağlamda birden çok kez gerekiyorsa sonucu bir
VAR‘a alın veyaADDCOLUMNSile bir kez hesaplayıp sütun olarak kullanın.
VAL parametreleri ise fonksiyona girmeden bir kez hesaplanır. Bağlamı değiştirmeniz gerekmiyorsa VAL hem daha öngörülebilir hem genellikle daha ucuzdur.
Hata ayıklama ipuçları
- Önce sorguda test edin. Fonksiyonu
DEFINE FUNCTIONile yazıpEVALUATEile bilinen girdilerle sınayın; modele ancak sonra kaydedin. - Ara değerleri loglayın. Microsoft’un kur dönüşümü örneğinde olduğu gibi
EVALUATEANDLOGile herVAR‘ın değerini izleyebilirsiniz. - Ölçün, tahmin etmeyin. Performans Çözümleyicisi ve DAX Studio’nun Server Timings görünümüyle fonksiyonlu ve fonksiyonsuz sürümü karşılaştırın.
- Tip uyarılarını ciddiye alın. Tabular Editor 3.26.2’nin anlamsal çözümleyicisi, bildirilen türle uyuşmayan argümanlarda artık uyarı veriyor.
Sınırlamalar ve tuzaklar
GA olması özelliğin eksiksiz olduğu anlamına gelmiyor. Üretime taşımadan önce bilmeniz gerekenler (Microsoft Learn belgelerine göre):
Tanım düzeyinde
- Özyineleme (recursion) ve karşılıklı özyineleme yok.
- Aşırı yükleme (overloading) yok; aynı adı farklı imzayla tanımlayamazsınız. Tip kontrolü fonksiyonlarıyla tek fonksiyonda dallanma bunun yerine geçer.
- Açık dönüş türü belirtilemez.
- UDF’ler
enumdeğer döndüremez; enum bekleyen yerleşik parametrelere UDF verilemez.
Model düzeyinde
- UDF’ler gizlenemez, görüntü klasörlerine konamaz ve çevirilerle (translations) birlikte kullanılamaz.
- Hiç tablo içermeyen modellerde desteklenmez.
- Nesne düzeyi güvenlik (OLS) fonksiyonlara geçmez. Korunan bir ölçüyü çağıran fonksiyon otomatik olarak korunmaz; erişimi olmayan kullanıcı için o ölçü yokmuş gibi davranır. Fonksiyon adlarında ve açıklamalarında gizli nesneleri ele vermeyin.
- Tablo öneki olmayan (nitelenmemiş) sütun başvuruları ölçü gibi yorumlanır; yeniden adlandırmada otomatik düzeltilmeyebilir. Her zaman
'Tablo'[Sütun]yazın.
Araç ve bağlantı düzeyinde
- Canlı bağlantılı raporlarda rapor ölçüleri kaynak modeldeki UDF’leri kullanabilir ama IntelliSense yoktur; bileşik (composite) modelde model ölçüleri kaynak modelin UDF’lerine başvuramaz.
- Görsel hesaplama formül çubuğunda UDF için IntelliSense yok; ayrıca görsel hesaplamalar yalnızca görseldeki alanları fonksiyona geçirebilir.
- VS Code TMDL eklentisinde IntelliSense sınırlı, SSMS’te yok. Sütunları EXPR parametresi olarak geçirirken editör yanlış kırmızı alt çizgi gösterebilir.
Sık yapılan hatalar
| Hata | Belirti | Çözüm |
|---|---|---|
| Bağlamı değiştirecek parametreyi VAL bırakmak | CALCULATE içindeki filtre etkisiz, önceki yıl = bu yıl | EXPR veya MEASUREREF kullanın |
EXPR ifadesini CALCULATE ile sarmamak | Ham SUMX gönderildiğinde boş/yanlış sonuç | Gövdede CALCULATE ( expr ) |
| Tam sayı alt türü seçmek | Ondalıklar sessizce yuvarlanır | DECIMAL, DOUBLE veya NUMERIC |
| Hesaplanan sütunda değişken dönüş türü | Tür hatası veya beklenmedik veri tipi | Sonucu CONVERT ile sabitleyin |
| Açıklamasız fonksiyon | Kimse ne yaptığını bilmez, katalog boş | Her fonksiyona /// açıklaması |
| Eski Tabular Editor sürümü | Varsayılan parametrelerde sahte hata | 3.26.2 veya üstü |
En iyi uygulamalar: kontrol listesi
- Her fonksiyonun
///açıklaması ve@param/@returnsetiketleri var. - Ad PascalCase ve bir isim alanı öneki taşıyor (
Satis.,Finans.). - Her parametrenin türü ve modu (VAL / EXPR) açıkça yazılmış.
- Bağlamı değiştiren parametreler EXPR ya da referans türünde ve gövdede
CALCULATEile sarılmış. - Birden çok kez kullanılan EXPR sonuçları
VAR‘da tutuluyor. - Tüm sütun başvuruları tablo önekli.
- Fonksiyon modele kaydedilmeden önce DAX sorgusunda bilinen girdilerle test edildi.
- İş kuralı ilgili birimle (finans, satış) mutabakatla tanımlandı; sahibi ve doğrulama tarihi kayıtlı.
functions.tmdlGit’te; değişiklikler kod incelemesinden geçiyor.- OLS ile korunan nesneler fonksiyon adında veya açıklamasında geçmiyor.
Sonuç: nereden başlamalı?
UDF’ler DAX’ı bir formül dilinden gerçek bir programlama diline bir adım daha yaklaştırıyor: soyutlama, yeniden kullanım, tipler, belgeler ve sürüm kontrolü. Bunun bedeli de yeni bir sorumluluk: fonksiyon artık bir ölçü değil, bir KPI tanımı ve onu yönetmek gerekiyor.
Başlamak için üç adım öneriyorum:
- Envanter çıkarın. Modelinizde en çok kopyalanan üç formülü bulun. Genellikle marj, geçen yıl karşılaştırması ve bir oran hesabı çıkar.
- Tanımı netleştirin. Her biri için tek cümlelik bir iş tanımı yazın ve ilgili birimle onaylayın.
- Dönüştürün ve ölçün. Onları UDF’ye çevirin, mevcut ölçüleri fonksiyonu çağıracak şekilde güncelleyin ve sonuçları eski değerlerle karşılaştırın.
Bir sonraki yazıda bu fonksiyonları Git’e bağlı ortak bir kütüphane modeline taşımanın adımlarını anlatacağım. Sizin modelinizde ilk fonksiyona dönüştüreceğiniz formül hangisi?
Kaynaklar
- DAX user-defined functions — Microsoft Learn
- DAX User-Defined Functions (Generally Available) — Power BI Updates Blog
- Introducing user-defined functions in DAX — SQLBI
- UDFs vs. calculation groups — SQLBI DAX Internals
- User-defined functions — SQLBI DAX Internals
- Introducing DAX Lib — SQLBI
- Introducing the DAX Package Manager — Tabular Editor
- When DAX UDF measures beat calculation groups for time intelligence — Tabular Editor
- Tabular Editor 3: June 2026 Release
- DAX User-Defined Functions — Tabular Editor Docs
- Power BI June 2026: DAX UDFs Are Generally Available — Enterprise DNA
HABERDAR OL
Yeni rehberler, kategorilere ayrılmış tek bir e-postada.
Her pazartesi, o hafta yayımlanan yazılar konu başlıklarına göre derlenmiş olarak gelen kutunda. Yeni yazı yoksa e-posta da yok.

1 yorum