İçeriğe geç

DAX’ta Yeni Dönem: Kullanıcı Tanımlı Fonksiyonlar (UDF)

DAX güncellendi 25.10.2026 18 dk okuma Atılay Berber

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önemNe oldu
Haziran 2026GA: isteğe bağlı parametreler, web’de modelleme, Model görünümünde düzenleme, varsayılan olarak açık
Haziran 2026Tabular Editor 3.26.2: GA sözdizimi ve tip uyuşmazlığı uyarıları
Aralık 2025SQLBI, topluluk fonksiyon kütüphanesi DAX Lib’i duyurdu
Eylül 2025Power 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ı denemeModel 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üzenlemecreateOrReplace 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şturmakFunctions › Yeni fonksiyon, formül çubuğunda düzenleme
Web’de modelleme (Service)Desktop’sız düzenleme, ekip içi iş birliğiGA ile geldi
Power BI Project (PBIP) + GitSürüm kontrolü, kod incelemesiFonksiyonlar definition/functions.tmdl dosyasında tutulur
Tabular Editor 3Gelişmiş yazar deneyimi, paket yönetimi3.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 LabsOnlarca modele otomatik dağıtımFabric 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ürNe kabul ederMod
AnyValSkaler veya tablo (varsayılan)val (expr verilebilir)
ScalarTek bir değer; alt türle daraltılabilirval (expr verilebilir)
TableBir tabloval (expr verilebilir)
AnyRefHerhangi bir model nesnesi referansıyalnızca expr
MeasureRefBir ölçü referansıyalnızca expr
ColumnRefBir sütun referansıyalnızca expr
TableRefBir tablo referansıyalnızca expr
CalendarRefBir 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 bir SUMX ifadesi 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:

  1. 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.
  2. 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.
  3. Bağlamı mod belirler. Varsayılan ifadenin hangi bağlamı miras alacağı parametrenin VAL ya da EXPR olmasına bağlıdır.
  4. 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, kurFinans.BrutMarj
Satis.Zaman karşılaştırmaları, sepet, fişSatis.GecenYilaGore
Stok.Devir hızı, stok günü, fireStok.StokGunu
Fiyat.Rakip endeksi, indirim derinliğiFiyat.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:

  1. 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.
  2. 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.MovingAverage paketi TimeSeries.MovingAverage.Simple fonksiyonunu 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çütUDFHesaplama grubu
ParametreAçık, tipli parametrelerYok; ayar filtre bağlamıyla gelir
Kim görürRaporda görünmez, geliştirici kullanırDilimleyicide, alan listesinde, filtrelerde görünür
UygulamaHer ölçüde farklı argümanlaBir görseldeki tüm ölçülere aynı dönüşüm
Çalışma biçimiÇağrıldığı yerde makro gibi açılırSorgu anında seçili ölçüleri dönüştürür
Ek yükParametreler yürütmeden önce çözülürSorgu 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:

  1. 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.
  2. 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.
  3. 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 veya ADDCOLUMNS ile 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 FUNCTION ile yazıp EVALUATE ile 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 EVALUATEANDLOG ile her VAR‘ı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 enum değ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

HataBelirtiÇözüm
Bağlamı değiştirecek parametreyi VAL bırakmakCALCULATE içindeki filtre etkisiz, önceki yıl = bu yılEXPR veya MEASUREREF kullanın
EXPR ifadesini CALCULATE ile sarmamakHam SUMX gönderildiğinde boş/yanlış sonuçGövdede CALCULATE ( expr )
Tam sayı alt türü seçmekOndalıklar sessizce yuvarlanırDECIMAL, DOUBLE veya NUMERIC
Hesaplanan sütunda değişken dönüş türüTür hatası veya beklenmedik veri tipiSonucu CONVERT ile sabitleyin
Açıklamasız fonksiyonKimse ne yaptığını bilmez, katalog boşHer fonksiyona /// açıklaması
Eski Tabular Editor sürümüVarsayılan parametrelerde sahte hata3.26.2 veya üstü

En iyi uygulamalar: kontrol listesi

  • Her fonksiyonun /// açıklaması ve @param / @returns etiketleri 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 CALCULATE ile 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.tmdl Git’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:

  1. 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.
  2. Tanımı netleştirin. Her biri için tek cümlelik bir iş tanımı yazın ve ilgili birimle onaylayın.
  3. 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

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.

Onay e-postasındaki bağlantıya tıklamadan liste aktif olmaz. İstediğin an tek tıkla çıkabilirsin.

Faydalı bulduysan paylaş
LinkedIn'de paylaş X WhatsApp

1 yorum

Yorum yaz

E-posta adresiniz yayınlanmayacak. Gerekli alanlar * ile işaretlenmişlerdir