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
- M, büyük/küçük harfe duyarlıdır:
Table.SelectRowsdoğru,table.selectrowshata. each [status] = "delivered", “her satır için” anlamına gelen kısa fonksiyon yazımıdır:(satir) => satir[status] = "delivered".{ }liste,[ ]kayıt (record),Tablo{0}ilk satır,Kayit[Alan]alan erişimi.
Sık kullanılan fonksiyonlar
| Fonksiyon | Ne yapar? |
|---|---|
Table.SelectRows | Satır süzme |
Table.AddColumn | Özel sütun: Table.AddColumn(t, "Tutar", each [Adet] * [Fiyat], type number) |
Table.TransformColumnTypes | Veri türü atama |
Table.Group | Gruplandırma |
Table.NestedJoin + Table.ExpandTableColumn | Birleştirme (Merge) |
Table.Combine | Ekleme (Append) |
Table.UnpivotOtherColumns | Özetten çıkarma |
Text.Trim, Text.Proper, Text.Lower | Metin temizliği |
Date.Year, Date.StartOfMonth | Tarih 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.
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:
insatı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.AddColumn | Satırın kaydı | each [Kanal] = "Web" |
Table.TransformColumns | Hücrenin değeri | each Text.Proper(_) |
List.Transform, List.Select | Listenin 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 nullkı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ştirme | Katlanır |
Satır başına try … otherwise, özel fonksiyon çağrısı, Table.Buffer | Katlanmaz; 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.