Ana içeriğe geç
Kesit Power BI
İleri · Veri Hazırlama

M dili: Power Query'nin arkasındaki kod

let/in yapısı, adımlar, sık kullanılan fonksiyonlar, özel fonksiyonlar ve hata yönetimi.

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

Yapı

let
    Kaynak = Sql.Database("sunucu", "AnadoluMarket"),
    Siparisler = Kaynak{[Schema="dbo", Item="orders"]}[Data],
    Teslim = Table.SelectRows(Siparisler, each [status] = "delivered"),
    Sutunlar = Table.SelectColumns(Teslim, {"order_id", "customer_id", "order_date"}),
    Adlar = Table.RenameColumns(Sutunlar, {{"order_id", "SiparisNo"}, {"customer_id", "MusteriNo"}})
in
    Adlar

Sık kullanılan fonksiyonlar

FonksiyonNe yapar?
Table.SelectRowsSatır süzme
Table.AddColumnÖzel sütun: Table.AddColumn(t, "Tutar", each [Adet] * [Fiyat], type number)
Table.TransformColumnTypesVeri türü atama
Table.GroupGruplandırma
Table.NestedJoin + Table.ExpandTableColumnBirleştirme (Merge)
Table.CombineEkleme (Append)
Table.UnpivotOtherColumnsÖzetten çıkarma
Text.Trim, Text.Proper, Text.LowerMetin temizliği
Date.Year, Date.StartOfMonthTarih işlemleri

Özel fonksiyon

let
    TemizSehir = (s as nullable text) as nullable text =>
        if s = null then null
        else Text.Proper(Text.Trim(s))
in
    TemizSehir

Bu sorguyu kaydedip başka sorgularda Table.TransformColumns(t, {{"city", TemizSehir}}) diye çağırabilirsin.

Hata yönetimi

Table.AddColumn(t, "TarihTemiz", each try Date.FromText([signup_date], [Culture="tr-TR"]) otherwise null)

try … otherwise, satır düzeyindeki hatayı yakalayıp yerine değer koyar. Hatalı satırları ayrı bir sorguda tutmak için Hataları Tut komutu kullanılır.

Uzmanlaş

Değerler: kayıt, liste, tablo

Power Query'de her adım bir değer üretir. Gelişmiş Düzenleyici'de (Advanced Editor) kodu okuyabilmek için dört temel değer türünü ve onlara erişimi tanımak yeter:

let
    Liste    = {"Web", "Mobil", "Mağaza"},               // liste: sıralı değerler
    Kayit    = [Ad = "Ece Kurt", Sehir = "İzmir"],       // kayıt: ad = değer çiftleri
    Tablo    = #table({"Kanal", "Pay"}, {{"Web", 0.37}, {"Mobil", 0.48}}),
    Ilk      = Liste{0},            // "Web"   — listede konum (0'dan başlar)
    Ad       = Kayit[Ad],           // "Ece Kurt" — kayıtta alan
    IlkSatir = Tablo{0},            // kayıt: [Kanal = "Web", Pay = 0.37]
    Kanallar = Tablo[Kanal],        // liste: {"Web", "Mobil"}
    Guvenli  = Kayit[Telefon]?      // alan yoksa hata yerine null
in
    Guvenli

Bir tablo, aynı alanlara sahip kayıtların listesi gibi düşünülebilir: Tablo{0} bir kayıt, Tablo[Kanal] bir listedir. ? işareti erişimi “isteğe bağlı” yapar: Liste{10}? ve Kayit[Telefon]? hata yerine null döndürür. Kaynaktan gelen sütun bazen yoksa (ör. CRM dışa aktarımı yeni bir sürümde sütunu kaldırdıysa) bu küçük işaret sorgunun tamamen çökmesini önler.

let/in: sıra değil, bağımlılık

let içindeki adlar bir tarif listesidir; yukarıdan aşağı çalıştırılmaz. M tembel (lazy) değerlendirir: in sonrasındaki değer hangi adlara ihtiyaç duyuyorsa yalnızca onlar hesaplanır. Bunun iki pratik sonucu var:

  • Hata ayıklama: in satırına ara bir adımın adını yazarak o adımın sonucunu görebilirsin. Uygulanan Adımlar bölmesinde bir adıma tıklamak da bunu yapar.
  • Kullanılmayan adım çalışmaz: bir adımdaki hata, sonuca giden yolda değilse sorguyu durdurmaz. Ama aynı adımı bir sonraki adım kullanmaya başladığı anda hata ortaya çıkar.

Boşluk ya da Türkçe karakter içeren adım adları #"Değiştirilen Tür" biçiminde yazılır. Arayüz bu adları kendisi üretir; elle yazarken kısa ve boşluksuz adlar (Turler, Filtre) kodu okunur kılar.

each ve _: kısa fonksiyon yazımı

each, tek parametreli bir fonksiyonun kısaltmasıdır: each [Adet] * [Fiyat] = (_) => _[Adet] * _[Fiyat]. Köşeli parantez içindeki alan adı, _ değerinin alanıdır. _ ne olduğuna göre değişir:

Fonksiyon_ nedir?Örnek
Table.SelectRows, Table.AddColumnSatırın kaydıeach [Kanal] = "Web"
Table.TransformColumnsHücrenin değerieach Text.Proper(_)
List.Transform, List.SelectListenin elemanıeach _ * 1.2
let
    Kaynak   = Satislar,
    Secili   = Table.SelectRows(Kaynak, each List.Contains({"Web", "Mobil"}, [Kanal])),
    Etiketli = Table.AddColumn(Secili, "Bant",
                 each if [Tutar] >= 2000 then "Büyük" else if [Tutar] >= 500 then "Orta" else "Küçük",
                 type text)
in
    Etiketli

İç içe each yazınca iki _ çakışır: içteki dıştakini gölgeler. Böyle durumlarda açık adlı fonksiyon yaz: (satir) => List.Select(Liste, (x) => x > satir[Esik]).

Özel fonksiyon ve parametreli kaynak

Satın alma ekibi her tedarikçi için ayrı Excel gönderiyor; dosyaların yapısı aynı. Aynı temizliği 8 kez yazmak yerine bir fonksiyon yazılır ve dosya listesinin her satırına uygulanır. “Klasörden birleştir” komutu bunu kendiliğinden yapar: bir örnek dosyayı dönüştür sorgusu ve ondan türetilen bir fonksiyon üretir; temizliği örnek sorguda yaptığında fonksiyon da güncellenir.

// fxTedarikciTemizle
(Dosya as binary, Tedarikci as text) as table =>
let
    Sayfa  = Excel.Workbook(Dosya, null, true){[Item = "Siparis", Kind = "Sheet"]}[Data],
    Baslik = Table.PromoteHeaders(Sayfa, [PromoteAllScalars = true]),
    Turler = Table.TransformColumnTypes(Baslik, {{"Tarih", type date}, {"Tutar", type number}}, "tr-TR"),
    Etiket = Table.AddColumn(Turler, "Tedarikci", each Tedarikci, type text)
in
    Etiket

Parametreli kaynak: sunucu ve veritabanı adlarını Parametreleri yönet ile SunucuAdi, VeritabaniAdi parametrelerine al: Sql.Database(SunucuAdi, VeritabaniAdi). Dağıtım işlem hattındaki parametre kuralları Test'te test sunucusunu, Üretim'de canlı sunucuyu seçer (Yaşam döngüsü dersi). Kenar durum: farklı kaynakları birleştiren sorgularda gizlilik düzeyleri uyuşmazsa Formula.Firewall hatası çıkar; kaynakları ayrı hazırlık (staging) sorgularına ayırmak ya da gizlilik düzeylerini doğru tanımlamak gerekir.

Hata yönetimi: try, otherwise, catch

İki tür hata vardır. Adım düzeyinde hata (dosya yok, sütun yok) sorgunun tamamını durdurur. Hücre düzeyinde hata (bir tarih çevrilemedi) yalnızca o hücreyi Error yapar; yüklemede o hücre boş gelir ve hata raporu oluşur. try her ikisini de bir kayda çevirir:

let
    Kaynak = Musteri,
    Denendi = Table.AddColumn(Kaynak, "Deneme", each try Date.FromText([KayitTarihi], [Culture = "tr-TR"])),
    // Deneme: [HasError = false, Value = ...]  ya da  [HasError = true, Error = [Reason, Message, Detail]]
    Tarih  = Table.AddColumn(Denendi, "KayitTarihiTemiz", each if [Deneme][HasError] then null else [Deneme][Value], type nullable date),
    Neden  = Table.AddColumn(Tarih, "HataNedeni", each if [Deneme][HasError] then [Deneme][Error][Message] else null, type nullable text),
    Sonuc  = Table.RemoveColumns(Neden, {"Deneme"})
in
    Sonuc
  • try … otherwise null kısa yoldur ama nedeni kaybeder. Kalite raporu gerekiyorsa yukarıdaki gibi neden sütunu tut; “2024-02-30” gibi hatalı satırlar sessizce kaybolmasın.
  • try … catch (e) => … yazımı hata kaydını doğrudan bir fonksiyona verir: try Number.From([Tutar]) catch (e) => if e[Message] = "…" then 0 else null.
  • Kendi hatanı da üretebilirsin: error "Beklenen Tutar sütunu yok". Kaynak beklenmedik biçimde değiştiğinde sessizce yanlış veri yüklemektense yenilemenin açık bir mesajla durması iyidir.

Sorgu katlama ile ilişkisi

Bir SQL kaynağında adımlar mümkün olduğunca tek bir SQL sorgusuna çevrilir (sorgu katlama, query folding). M yazım biçimin bunu doğrudan etkiler:

AdımÇoğu SQL kaynağında
Satır filtresi, sütun seçme/kaldırma, gruplama, aynı kaynaktaki birleştirmeKatlanır
Satır başına try … otherwise, özel fonksiyon çağrısı, Table.BufferKatlanmaz; sonraki adımlar da yerelde çalışır
Dizin sütunu ekleme, bazı metin fonksiyonları (ör. her sözcüğün ilk harfi)Genellikle katlanmaz

Kural: katlanan adımları başa, katlanmayanları sona koy. Adıma sağ tıklayıp Yerel sorguyu görüntüle (View Native Query) seçeneğinin açık olması, o adıma kadar katlandığını gösterir. Artımlı yenilemedeki RangeStart/RangeEnd filtresinin katlanması özellikle kritiktir; o filtreden önce katlamayı bozan bir adım varsa her bölüm için kaynağın tamamı okunur.

Kontrol listesiAdım adları okunur mu? · Hatalar null'a gömülmek yerine neden sütununda mı? · Kaynak adresleri parametrede mi? · Katlamayı bozan adımlar sonda mı? · Tekrarlanan temizlik fonksiyona alındı mı?
Alıştırmalara geç (9)