Ana içeriğe geç
Kesit Power BI
İleri · İleri Modelleme

Çoka çok ilişkiler, köprü tablolar ve ilişki türleri

Gerçek çoka-çok senaryoları (bir proje birden çok departmanda, bir müşteri birden çok kategoride, bütçe farklı taneciklikte), köprü tablo ve ağırlık, çift yönlü filtrenin riskleri ve belirsiz yollar, sınırlı (limited) ilişkiler, CROSSFILTER ve TREATAS ile ölçü içinde çözüm, toplam satırı ve çift sayma.

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ç.

Yıldız şemada her ilişki çoka-birdir: bir satış satırı tek bir ürüne, tek bir müşteriye bağlanır. Gerçek iş hayatı bu kadar düzenli değildir. “Model tasarımı II” dersinde ayrıntı düzeyini (grain), “Rol oynayan boyutlar” dersinde CROSSFILTER’ı, “Tablo fonksiyonları” dersinde köprü tablonun kısa tanımını gördün. Bu ders, çoka-çok (many-to-many) durumları tanımayı, modelde ve ölçüde çözmeyi ve toplam satırını kullanıcıya doğru anlatmayı öğretiyor.

Anadolu Market’te bu hafta gelen üç talep:

1 · Çoka-çok’un üç yüzü

TürÖrnekTipik çözüm
İki boyut arasında çok değerli ilişkiBir proje birden çok departmanda, bir departmanın birden çok projesi varKöprü tablo (ProjeDepartman), gerekirse ağırlık sütunu
Olgu üzerinden iki boyutBir müşteri birden çok kategoriden alır, bir kategoriden birçok müşteri alırOlgu tablosu zaten köprüdür; ölçüde küme filtresi ya da CROSSFILTER
Farklı taneciklikte olguHedefler kategori × ay, satış ürün × günOrtak boyut (Kategoriler); olmazsa TREATAS ya da çoka-çok kardinalite

Hepsinin ortak sonucu aynıdır: satırların toplamı, toplam satırına eşit değildir. Bir proje iki departmanda görünür, toplamda bir kez sayılır. Bu bir hata değil, çoka-çok’un doğasıdır; hata, bunu kullanıcıya söylememektir.

2 · Köprü tablo: proje ↔ departman

Modelde Projeler[DepartmanNo] tek bir departman tutuyor. Ortak projeleri göstermek için bir köprü tablo gerekir: her satırı “bu proje, bu departmana, şu payla aittir” cümlesidir. Modelde şöyle kurulur:

Departmanlar[DepartmanNo]  1 ──> *  ProjeDepartman[DepartmanNo]
Projeler[ProjeNo]          1 <─> *  ProjeDepartman[ProjeNo]     (çift yönlü)

ProjeDepartman: ProjeNo | DepartmanNo | Pay
                5       | 2 (Pazarlama)      | 0,7
                5       | 1 (Satış)          | 0,3
                7       | 3 (Veri Analitiği) | 0,6
                7       | 4 (Yazılım)        | 0,4
               12       | 3 (Veri Analitiği) | 0,5
               12       | 1 (Satış)          | 0,3
               12       | 2 (Pazarlama)      | 0,2

Filtrenin Departmanlar’dan Projeler’e ulaşması için köprü ile Projeler arasındaki ilişkinin çift yönlü olması gerekir: filtre Departmanlar → köprü yönünde doğal akar, köprü → Projeler yönünde ise “çoktan bire” geri gitmesi gerekir. Bu uygulamanın modelinde köprü tablo yok; aynı mantığı ölçünün içinde, sanal bir köprüyle deneyebilirsin. Önce ağırlıklı (paylaştırılmış) bütçe:

Paylaştırılmış Bütçe =
VAR Kopru =
    DATATABLE(
        "ProjeNo", INTEGER, "DepartmanNo", INTEGER, "Pay", DOUBLE,
        {
            {5, 2, 0.7}, {5, 1, 0.3},
            {7, 3, 0.6}, {7, 4, 0.4},
            {12, 3, 0.5}, {12, 1, 0.3}, {12, 2, 0.2}
        }
    )
VAR Gorunen = FILTER(Kopru, [DepartmanNo] IN VALUES(Departmanlar[DepartmanNo]))
RETURN
    SUMX(Gorunen, [Pay] * LOOKUPVALUE(Projeler[Butce], Projeler[ProjeNo], [ProjeNo]))

Sonra ağırlıksız, “dokunduğu projelerin tam bütçesi”:

Katıldığı Projelerin Bütçesi =
VAR Kopru =
    DATATABLE(
        "ProjeNo", INTEGER, "DepartmanNo", INTEGER, "Pay", DOUBLE,
        {
            {5, 2, 0.7}, {5, 1, 0.3},
            {7, 3, 0.6}, {7, 4, 0.4},
            {12, 3, 0.5}, {12, 1, 0.3}, {12, 2, 0.2}
        }
    )
VAR Projeler_ = SELECTCOLUMNS(FILTER(Kopru, [DepartmanNo] IN VALUES(Departmanlar[DepartmanNo])), "P", [ProjeNo])
RETURN
    CALCULATE([Proje Bütçesi], REMOVEFILTERS(Departmanlar), TREATAS(Projeler_, Projeler[ProjeNo]))
DepartmanPaylaştırılmışTam bütçe
Pazarlama165.000 ₺450.000 ₺
Satış135.000 ₺450.000 ₺
Veri Analitiği402.000 ₺720.000 ₺
Yazılım168.000 ₺420.000 ₺
Satırların toplamı870.000 ₺2.040.000 ₺
Toplam satırı870.000 ₺870.000 ₺

Üç projenin gerçek bütçesi 870.000 ₺. Paylaştırılmış ölçü toplanabilir: satırlar toplama eşittir. Tam bütçe ölçüsü toplanamaz: her departman “benim projem” der, satırlar 2.040.000 ₺ eder. İkisi de doğrudur, ama farklı soruları cevaplar. Departman müdürü “sorumlu olduğum işlerin büyüklüğü”nü, finans “maliyetin kime yazılacağı”nı sorar. Raporda ikisini aynı ada koyma.

Üretimde köprüDATATABLE yalnızca deneme ve öğretim içindir. Gerçek köprü tablo kaynakta ya da Power Query’de üretilir; payların her proje için 1’e tamamlandığını yenilemeden sonra bir sorguyla doğrula. Fiziksel tasarımı (vekil anahtar, köprünün ambarda nasıl tutulduğu) “Fabric’te yıldız şema” dersi anlatıyor.

3 · Olgu tablosu bir köprüdür: müşteri ↔ kategori

Pazarlamanın sorusu: “Elektronik alanların toplam cirosu.” Burada köprü tablo kurmana gerek yok; Satislar zaten her müşteriyi aldığı kategorilere bağlıyor. Yapılacak iş, iki adımlı bir filtre:

  1. Görünen kategoride alışveriş yapan müşterilerin kümesini bul.
  2. Kategori filtresini kaldır, bu müşterilerin tüm satışını hesapla.
Alıcıların Toplam Satışı =
VAR Alicilar = CALCULATETABLE(VALUES(Satislar[MusteriNo]), Satislar[Durum] <> "İptal")
RETURN
    CALCULATE([Net Satış], Alicilar, REMOVEFILTERS(Kategoriler), REMOVEFILTERS(Urunler))
KategoriAlıcı sayısıKategorideki satışAlıcıların toplam satışı
Elektronik95339.304 ₺1.104.856 ₺
Kitap113151.882 ₺1.153.184 ₺
Hediye3017.370 ₺328.027 ₺
Toplam satırı1361.250.772 ₺1.250.772 ₺

Kitap alıcıları, şirket cirosunun %92’sini yapıyor: Kitap küçük bir kategori ama çok geniş bir müşteri kitlesine dokunuyor. Bu bilgi kategori satışından görünmez. Yedi kategorinin alıcı sayılarını toplarsan 628 eder, oysa tekil müşteri 136’dır. Bir müşteri ortalama 4,6 kategoride görünür.

Tek yönlü filtrenin tuzağı

“En az 4 kategoriden alan müşteri” sorusunda çoğu kişi CALCULATE(DISTINCTCOUNT(Urunler[Kategori])) yazar ve her müşteride 7 bulur. Sebep: filtre Satislar’dan Urunler’e akmaz. Müşteri filtresi Satislar’ı süzer ama Urunler tablosunun bütün satırları görünür kalır. Çözüm ya ölçü içinde CROSSFILTER(Satislar[UrunNo], Urunler[UrunNo], BOTH) ya da olgu üzerinden saymak: COUNTROWS(SUMMARIZE(Satislar, Urunler[Kategori])). İkisiyle de dağılım şöyle çıkar: 7 kategoride 13, 6’da 40, 5’te 22, 4’te 28 müşteri; yani 136 müşterinin 103’ü en az 4 kategoriden almış.

4 · Çift yönlü filtre: ne zaman, neden riskli?

Çift yönlü (both) ilişki, filtrenin bir taraftan çok tarafa olduğu gibi çok taraftan bir tarafa da akmasını sağlar. Kolay görünür; üç bedeli vardır.

Bedel 1: anlam sessizce değişir

Kanal kırılımında CALCULATE([Kayıtlı Müşteri], CROSSFILTER(Satislar[MusteriNo], Musteriler[MusteriNo], BOTH)) Mağaza’da 73 verir. [Müşteri Sayısı] ise 70. Fark, bu kanaldaki siparişlerinin hepsi iptal olan 3 müşteri: çift yönlü filtre “bu kanalda satırı olan” müşteriyi sayar, iş kuralını (iptal hariç) bilmez. Kullanıcı modelde ilişkiyi çift yönlü görünce bütün müşteri sayılarının bu yolla geldiğini varsayar.

Bedel 2: belirsiz yollar

Modelde Talepler hem Tarih’e hem Musteriler’e bağlı. Satislar–Musteriler ilişkisini çift yönlü yaparsan, Tarih’ten Talepler’e iki yol oluşur: doğrudan Tarih → Talepler ve Tarih → Satislar → Musteriler → Talepler.

Yol 1: Tarih ──> Talepler
Yol 2: Tarih ──> Satislar ──(çift yönlü)──> Musteriler ──> Talepler

Microsoft Learn’e göre Power BI bu durumda yolları önceliğe göre sıralar (önce doğrudan bire-çok yollar), eşit öncelik ve ağırlıkta birden çok yol kalırsa “belirsiz yol” hatası verir. Desktop çift yönlü değişikliği kaydetmeyi reddedebilir ya da belirsizliğe izin verip bir yolu seçebilir. Her iki durumda da [Talep Sayısı] gibi masum bir ölçünün anlamı, modeli kimin ne zaman değiştirdiğine bağlı hâle gelir.

Bedel 3: performans ve güvenlik

Çift yönlü ilişki her sorguda ek filtre yayılımı demektir ve büyük modellerde belirgin biçimde yavaştır. Satır düzeyi güvenlikte (RLS) güvenlik filtresi varsayılan olarak tek yönde akar; ilişki özelliklerindeki “Güvenlik filtresini her iki yönde de uygula” seçeneği açılmadıkça çift yönlü ilişki RLS’yi taşımaz. Bu seçeneği açmak ise rol tasarımını karmaşıklaştırır.

KuralModeldeki ilişkiler tek yönlü kalsın. Çift yönlü davranışa ihtiyaç duyan her ölçü bunu CROSSFILTER(…, BOTH) ile, açıkça ve yalnızca kendi içinde istesin. İstisna: köprü tablo ile ona bağlı boyut arasındaki ilişki; orada çift yön desenin parçasıdır ve belgelenmelidir.

5 · Farklı taneciklik: hedef kategori düzeyinde, rapor ürün tablosundan

Satış planlamanın sorunu: görselde kategoriyi Urunler[Kategori]’den kullanınca [Hedef] her satırda 1.299.300 ₺ gösteriyor. Hedefler, Urunler’e bağlı değil; Kategoriler üzerinden bağlı. Üç çözüm var:

YolNasıl?ArtıEksi
Ortak boyutGörselde Kategoriler[Kategori]; Urunler[Kategori] gizlenirHızlı, düzenli ilişki, ölçü değişmezKullanıcıyı doğru alana yönlendirmek gerekir
TREATAS (sanal ilişki)Ölçü içinde Urunler[Kategori] değerleri Hedefler[Kategori]’ye uygulanırModele dokunmaz, hangi ölçüde geçerli olduğu açıkHer ölçüye ayrı yazılır, bakım yükü
Çoka-çok kardinaliteHedefler[Kategori] *—* Urunler[Kategori] ilişkisiKodsuz, sürükle-bırakSınırlı ilişki: yavaş, RELATED yok, boş satır yok
Hedef (Ürün Kategorisinden) =
CALCULATE([Hedef], TREATAS(VALUES(Urunler[Kategori]), Hedefler[Kategori]))

Urunler[Kategori] kırılımında artık Elektronik 351.800 ₺, Kitap 156.200 ₺ gösterir; toplam 1.299.300 ₺.

Sınırlı (limited) ilişki ne demek?

Learn’e göre bir ilişki, motor “bir” tarafını garanti edemediğinde sınırlıdır: çoka-çok kardinalite (sütunlar benzersiz olsa bile) ve bileşik modellerde farklı kaynak grupları arasındaki ilişkiler. Sınırlı ilişkinin davranışı düzenli ilişkiden farklıdır:

-- Model görünümünde (bu uygulamanın motorunda kurulamaz)
Hedefler[Kategori]  *  ──  *  Urunler[Kategori]
Çapraz filtre yönü: Tek (Urunler filtreler Hedefler’i)

6 · Ortak boyut mu, çift yönlü mü, TREATAS mı? Toplam satırındaki fark

Gider türüne göre proje bütçesini görmek isteyelim. Giderler ve Projeler, Departmanlar üzerinden birbirine dokunuyor; ama gider türü filtresinin projelere ulaşması için filtrenin Giderler’den Departmanlar’a geri akması gerekir.

Gider Türü Proje Bütçesi (CROSSFILTER) =
CALCULATE([Proje Bütçesi], CROSSFILTER(Giderler[DepartmanNo], Departmanlar[DepartmanNo], BOTH))

Gider Türü Proje Bütçesi (TREATAS) =
CALCULATE([Proje Bütçesi], TREATAS(VALUES(Giderler[DepartmanNo]), Departmanlar[DepartmanNo]))
Gider türüGideri yapan departmanİki ölçü de
PersonelSatış45.000 ₺
Lojistik ve kargoSatış45.000 ₺
Pazarlama ve reklamPazarlama240.000 ₺
TeknolojiYazılım1.140.000 ₺
Kira ve tesis, Genel yönetimGenel Müdürlük (projesi yok)BLANK
Toplam satırıCROSSFILTER: 3.040.000 ₺ · TREATAS: 1.425.000 ₺

Satırlarda iki ölçü aynı. Toplamda ayrılıyorlar: CROSSFILTER’da Giderler hiç filtrelenmemişse Departmanlar’a yayılacak bir filtre yoktur ve bütün projeler (3.040.000 ₺) gelir. TREATAS ise her zaman “gideri olan departmanlar” kümesini uygular ve Veri Analitiği, Finans, İK, Hukuk projelerini dışarıda bırakır (1.425.000 ₺). Satırların toplamı ise 1.470.000 ₺’dir, çünkü Satış departmanının 45.000 ₺’lik projesi iki gider türünde görünür. Üç farklı “toplam” var; hangisinin doğru olduğunu iş sorusu belirler, DAX değil.

Karar listesi ve test

  1. Gerçekten çoka-çok mu? Önce ortak bir boyut ara. Hedef–satış sorununun %90’ı ortak boyutla çözülür.
  2. Köprü gerekiyorsa payların toplamını doğrula, köprü–boyut ilişkisini çift yönlü yap ve bunu ilişki açıklamasına yaz.
  3. Ölçü içinde çöz: Model tek yönlü kalsın; CROSSFILTER ya da TREATAS yalnızca gereken ölçüde.
  4. Toplanabilir mi? Paylaştırılmış ölçü toplanır, tam değer ölçüsü toplanmaz. Ölçü adına ve araç ipucuna bunu yaz: “Toplam satırı tekil projeleri bir kez sayar.”
  5. Test: DAX sorgu görünümünde SUMX(VALUES(Boyut[Anahtar]), [Ölçü]) ile toplam satırını yan yana koy. Fark beklediğin fark mı?
  6. RLS: Köprü ya da çift yönlü ilişki varsa, güvenlik filtresinin hangi yönde aktığını rol görünümüyle (Farklı görüntüle) kontrol et.
  7. Performans: Performans Analizi’nde çift yönlü ilişkili görselin süresini, CROSSFILTER’lı ölçü sürümüyle karşılaştır.
Alıştırmalara geç (9)