Kapitoly kurzu
1. Obsahová mapa kurzu 2. Notebooky: PySpark vs Pure Python 3. Demo: Tři cesty a Mini Medallion 4. CU Costs a Capacity Metrics 5. SQL trojúhelník 6. Real-Time Intelligence: demo arc 7. Semantic modely, DAX a optimalizace 8. Security a administrace Fabricu 9. Zkouška DP-600: tipy
Runbooky — medallion
10. Mini Medallion ČHMÚ 11. Mini Medallion NYC Taxi 12. NYC Taxi třemi cestami 13. Mini Medallion OpenAQ 14. Big Medallion OpenAQ (2 roky)
Runbooky — RTI
15. RTI Library: architektura 16. A: Bicycles 60s 17. B: ČHMÚ HTTP polling 18. C: NDIC dopravní detektory 19. D: Camera ALPR simulátor 20. E: PID GTFS-RT 21. F: Activator alerty 22. KQL cheat sheet
🏠 Rozcestník kurzu
DP-600 · Kapitola 7

Semantic modely, DAX a optimalizace

Storage mody, Direct Lake, mental model DAXu, RLS / OLS a toolset pro výkon
🗂 Kapitola kurzu

Cíle kapitoly

  • Vybrat storage mode semantic modelu (Import, DirectQuery, Direct Lake) podle dat a požadavků
  • Vysvětlit row context, filter context a CALCULATE na příkladech nad daty ČHMÚ
  • Rozumět framingu a fallbacku Direct Lake a jeho limitům
  • Projít optimalizační workflow s Performance Analyzerem, DAX Studiem a VertiPaq Analyzerem
  • Nastavit RLS / OLS a chápat propagaci sensitivity labelů
7.1

Úvod

Poznámky a demo runbook pro Day 3. Audience má SQL/PBI background — DAX intro lze zkrátit, optimalizace deep-dive.

ℹ️
Demo data: gold layer z Days 1-2 — gold_fact_kvalita_ovzdusi + dim_station, dim_kraj, dim_pollutant, dim_date. Žádný nový dataset, žádný nový workspace.
7.2

Semantic Model architektura — kdy co

SCHÉMA
┌─────────────────────────────────────────────────────────────┐
│ STORAGE MODE per table v semantic modelu                     │
│                                                               │
│  Import           DirectQuery        Direct Lake              │
│  ──────────       ─────────────      ───────────────          │
│  • Data v PBI    • Query → source    • OneLake Delta          │
│    cache (.pbix)   each interaction    no-copy reference      │
│  • Refresh =     • Live but slow     • Framing každých ~10min │
│    full reload   • Cap 1M rows/visual • Sub-sec performance   │
│  • Best perf     • RLS pass-through  • F-SKU only             │
│  • Cap dataset                       • Fallback to DQ on miss │
│    size (10GB    Použít: real-time   Použít: large fact +     │
│    Premium)      malé dotazy          PBI experience          │
└─────────────────────────────────────────────────────────────┘

Decision tree

SCHÉMA
Data velikost > 10GB?
├── ANO → Direct Lake (default pro Fabric semantic modely)
└── NE
    │
    Real-time refresh required (sub-min)?
    ├── ANO → DirectQuery (na DW/SQL DB) nebo Direct Lake nad streamem
    └── NE → Import (best performance)

Composite model (Day 4 RTI integration)

PBI semantic model může mít mix storage modes per table:

  • dim_station → Direct Lake (Lakehouse)
  • gold_fact_kvalita_ovzdusi → Direct Lake (Lakehouse, batch historic)
  • mv_b_chmi_last_per_station → DirectQuery (KQL DB, real-time current state)

Měřítka mohou kombinovat sloupce z různých storage modes — RELATED přes vztahy. Limit: cross-storage joins jsou drahé, vyhni se velkým fact-fact joins napříč storage modes.

7.3

DAX — co audience musí pochopit

Mental model: 2 contexts

DAX má dvě "okna" do dat:

  1. Row context — kde právě jsem v tabulce (jeden řádek)
  2. Filter context — co je filtrováno na visual / report
DAX
-- Row context: SUMX iteruje řádky
Total Revenue = SUMX(Sales, Sales[Quantity] * Sales[UnitPrice])

-- Filter context: SUM bere agregát
Total Revenue Simple = SUM(Sales[Total])
💡
Klíčový insight: SQL nemá row context out-of-the-box, jen aggregations + WINDOW functions. DAX iterators (SUMX, AVERAGEX, MAXX, ...) jsou ekvivalent SQL window functions, ale s implicit row context.

CALCULATE = 50% DAX power

DAX
-- Modify filter context
Revenue YTD = CALCULATE([Total Revenue], DATESYTD('Date'[Date]))

-- Remove filter (ALL)
Revenue All Time = CALCULATE([Total Revenue], ALL('Date'))

-- Replace filter
Revenue Praha = CALCULATE([Total Revenue], 'dim_station'[kraj_kod] = "PHA")

-- Combine
Revenue YTD vs LY = 
VAR ytd = CALCULATE([Total Revenue], DATESYTD('Date'[Date]))
VAR ly = CALCULATE([Total Revenue], DATESYTD(SAMEPERIODLASTYEAR('Date'[Date])))
RETURN ytd - ly

Time intelligence — must-know pattern

DAX
PM10 Avg Today = AVERAGE(gold_fact_kvalita_ovzdusi[pm10])

PM10 Avg YTD = 
CALCULATE([PM10 Avg Today], DATESYTD('Date'[Date]))

PM10 Avg Same Period Last Year = 
CALCULATE([PM10 Avg Today], SAMEPERIODLASTYEAR('Date'[Date]))

PM10 YoY % = 
DIVIDE(
    [PM10 Avg YTD] - [PM10 Avg Same Period Last Year],
    [PM10 Avg Same Period Last Year]
)

CALCULATE filter context modifier reference (top 10)

Modifier Co dělá SQL ekvivalent
ALL(table) Remove all filters from table OVER()
ALL(col) Remove filter from one column OVER (PARTITION BY ...) excluding col
ALLEXCEPT(table, col1, col2) Remove all filters except listed –
KEEPFILTERS(filter) Add to existing filter, don't override –
REMOVEFILTERS(table/col) Same as ALL but more explicit (DAX 2019+) –
USERELATIONSHIP(col1, col2) Activate inactive relationship –
CROSSFILTER(col1, col2, dir) Override cross-filter direction –
FILTER(table, predicate) Row-by-row filter (slow on large tables) WHERE
TREATAS({values}, col) Apply virtual filter from values IN (...)
SELECTEDVALUE(col) Get single value if filter has 1 row –
7.4

Demo runbook (live)

Setup (před lekcí)

  • Workspace DP600_Demo_CHMI s gold layerem z Days 1-2
  • PBI Desktop nebo Power BI Service workspace
  • New semantic model: Direct Lake nad gold tables

Demo 1: Build semantic model

  1. Lakehouse → New semantic model → vyber gold_fact_kvalita_ovzdusi, dim_station, dim_kraj, dim_pollutant, dim_date
  2. Otevři Model view — auto-detect relationships (zkontroluj že FKs sedí)
  3. Hide ID columns, mark dim_date[date] as date table
  4. Live demo: refresh = framing (rychlé)

Demo 2: 3 measures progresivně

DAX
-- Lekce: aggregate, no context manipulation
Avg PM10 = AVERAGE(gold_fact_kvalita_ovzdusi[pm10])

-- Lekce: time intelligence
Avg PM10 YTD = CALCULATE([Avg PM10], DATESYTD('dim_date'[date]))

-- Lekce: ranking + topN
Top 5 Stations PM10 = 
RANKX(
    ALL('dim_station'[station_nazev]),
    [Avg PM10],
    ,DESC,Dense
)

Postaveno na obrazovce — matrix dim_kraj × dim_pollutant na value [Avg PM10], pak přidat [Avg PM10 YTD], pak slicer dim_date.

Demo 3: RLS

  1. Modeling → Manage roles → New: Kraj_Filter
  2. Table dim_station → DAX rule:
DAX
[kraj_kod] = LOOKUPVALUE(
    'dim_user'[kraj_kod],
    'dim_user'[email], USERPRINCIPALNAME()
)
  1. Test as role — vidíš jen jeden kraj
  2. Publish → assign role v Power BI Service

Demo 4: Performance Analyzer + DAX Studio

  1. PBI Desktop → View → Performance Analyzer → Start
  2. Refresh visual, zkopíruj query
  3. Otevři DAX Studio, paste query
  4. Run with Server Timings — vidíš FE vs SE split, Storage Engine query plan
  5. Iterate: optimize measure, re-measure
7.5

Direct Lake deep-dive

Co to je

  • Semantic model nereaduje data do paměti při refreshu
  • Místo toho referencuje Delta soubory v OneLake přímo
  • Při query: VertiPaq engine on-demand loaduje column dat z Delta

Framing

  • Power BI průběžně sleduje Delta _delta_log; při detekci nové verze proběhne framing (automatické aktualizace lze vypnout a řídit refresh ručně)
  • Pokud nová verze: framing (lightweight metadata reload, ne data reload)
  • User vidí fresh data bez explicit refresh button

Fallback to DirectQuery

  • Týká se Direct Lake on SQL endpoint: model spadne do DirectQuery přes SQL analytics endpoint, když překročí guardrail kapacity (počet řádků, velikost modelu), když tabulka stojí na SQL view, nebo když endpoint vynucuje RLS/OLS. „Studené“ sloupce se jen dočtou do paměti — fallback nezpůsobí
  • Indikováno warning v Performance Analyzer: Storage Mode = DirectQuery
  • Hot point pro audience: monitorovat fallback (Performance Analyzer, DAX Studio) — je to signál překročeného guardrailu nebo view v modelu. Direct Lake on OneLake (novější varianta) do DirectQuery nepadá vůbec a umí i tabulky z více zdrojů; volba mezi oběma variantami je explicitní cíl zkoušky

Limitace Direct Lake

  • F-SKU only (ne Pro/PPU)
  • Guardraily per kapacita — řádků na tabulku: F2–F32 300 M, F64 1,5 mld., F128 3 mld.; velikost modelu F2–F8 10 GB, F16 20 GB, F32 40 GB (přesné hodnoty ověřte v dokumentaci)
  • Některé DAX funkce nejsou supported (USERELATIONSHIP s caveats, COLUMNSTATISTICS, ...)
  • Calculated columns / tables: na Direct Lake on SQL endpoint nejsou podporované (počítejte je v Lakehouse jako materializované sloupce); Direct Lake on OneLake je má v preview — ověřte aktuální stav

Cost

  • Direct Lake = single capacity unit pricing (na rozdíl od Pro $14/user, od dubna 2025)
  • Pro 8 účastníků s F2 capacity: ~$200/měsíc, sdílené
  • Vs Premium per user (PPU): 8 × $24 = $192/měsíc, ale bez Lakehouse, bez Spark
7.6

Performance & optimization workflow

Step 1: Identifikuj slow visual

  • PBI Desktop → View → Performance Analyzer → Start recording
  • Interaguj s reportem (filtry, slicery)
  • Stop → tabulka per-visual: DAX query time + Visual display time

Step 2: Analyze query

  • Click "Copy query" na slow visual
  • Otevři DAX Studio (free, https://daxstudio.org/)
  • Connect k tvému PBI model (Desktop nebo Service)
  • Paste query → enable Server Timings → Run
  • View pane: Storage Engine queries + Formula Engine time breakdown

Step 3: Optimize

Common patterns:

  • Replace iterators s vector ops kde možné (SUMX(table, [col]) → SUM(table[col]))
  • Reduce row context transitions — vyhni se [measure] v FILTER predikátu
  • Use variables (VAR x = ...) pro reuse výpočtu
  • CALCULATE filter lépe než FILTER(ALL(...), ...) pro simple equality

Step 4: VertiPaq Analyzer (storage optimization)

  • DAX Studio → VertiPaq Analyzer view
  • Per-table per-column metrics: cardinality, dictionary size, columns size
  • Top offenders typically:
    • High cardinality columns (timestamps with seconds → bin to minute)
    • Free-text columns (PII, descriptions → hash or remove)
    • Calculated columns (compute v Lakehouse místo v PBI)

Step 5: Tabular Editor 2 (free)

  • Bulk edit measures, perspectives, calc groups
  • Best Practice Analyzer (BPA) — automatic anti-pattern detection
  • Edit RLS roles bulk

Doporučený toolset (handout)

Tool Cena Co dělá Kdy použít
Performance Analyzer (built-in) free Identifikuj slow visual první krok vždy
DAX Studio free Profiling, Server Timings každý slow query
VertiPaq Analyzer free (in DAX Studio) Storage analysis optimalizace size
Tabular Editor 2 free Bulk edit každý větší model
Tabular Editor 3 $200/year TE2 + advanced (script library, BPA, deployments) enterprise
ALM Toolkit free Schema diff + deploy CI/CD scénáře
BI Helper free Documentation gen dokumentace modelů
7.7

RLS / OLS / Sensitivity labels

RLS (Row-Level Security)

  • DAX expression na tabulce, vyhodnocuje se pre-query
  • 2 modes: Static (filter literal value) nebo Dynamic (USERPRINCIPALNAME() based)
  • Test v PBI Desktop: Modeling → View as → Other user
DAX
-- Static
[kraj_kod] = "PHA"

-- Dynamic via dimension table
[kraj_kod] IN
SELECTCOLUMNS(
    FILTER('dim_user', 'dim_user'[email] = USERPRINCIPALNAME()),
    'dim_user'[kraj_kod]
)

OLS (Object-Level Security)

  • Hide tables/columns based on role
  • Configured via Tabular Editor (PBI UI does not have native UI yet)
  • Model definition (TMSL/TMDL):
TMDL
table dim_salary
    tablePermissions Manager: ReadAll, Worker: None

Sensitivity Labels (MIP)

  • Microsoft Information Protection labels propagated z OneLake
  • Auto-applied na PBI semantic model na základě upstream Lakehouse
  • Lze override v PBI ručně (audit log records change)
  • Downstream: PBI report inheritne label, export má watermark

3-layer security mental model

SCHÉMA
Layer 1: Workspace role (Admin/Member/Contributor/Viewer)
   └── kdo vidí workspace a co může v něm dělat
        Layer 2: Item permission (semantic model Read/Build)
           └── kdo může konzumovat / build na top
                Layer 3: RLS / OLS / Labels
                   └── jaká data konkrétní user vidí
7.8

Day 3 hands-on labs (konsolidovaný blok)

Doporučené laby z MS Learn (vybrat 1-2):

Lab Učební cíl
Lab 5: Build semantic model from Lakehouse Direct Lake basics
Lab 6: Write DAX measures CALCULATE + time intelligence
Lab 7: Implement RLS Dynamic RLS s user mapping
Lab 8: Optimize semantic model DAX Studio + VertiPaq

Pokud má tým SQL background a žádný DAX → preferuj Lab 6. Pokud audience má PBI experience → preferuj Lab 8 (advanced).

7.9

Day 3 troubleshooting

Problem Fix
Direct Lake fallback to DQ Performance Analyzer → check warning. Příčina: překročený guardrail (řádky / velikost modelu), view místo tabulky, nebo RLS na SQL endpointu. Fix: zmenšit model / zvednout F-SKU / přejít na Direct Lake on OneLake.
RLS test "View as role" prázdná tabulka DAX expression vrací FALSE pro user. Check USERPRINCIPALNAME() mapping table.
Time intelligence neproveze dim_date musí být marked as date table, contigous range, no gaps.
CALCULATE warning "Filter argument modifies columns from multiple tables" Use KEEPFILTERS nebo split CALCULATE do více volání.
Slow visual po publish ale rychlý v Desktop Capacity throttling. Capacity Metrics App → Throttling tab.
7.10

Reference