Hiërarchische RLS in Power BI: managers zien alleen hun eigen team
Power BI architect, LSS Black Belt. 15 jaar ervaring in data & business intelligence.

Row Level Security — RLS — is het mechanisme in Power BI waarmee je bepaalt welke rijen een gebruiker mag zien op het moment dat hij een rapport opent. In een HR-context is dat niet zomaar een technische keuze, maar een AVG-verplichting. Een teamleider die verzuimcijfers van een andere afdeling kan inzien, is niet een 'handigheidje' maar een datalek.
Het lastige zit niet in de basisconfiguratie van RLS — één statische rol bouwen is een kwartiertje werk. Het lastige zit in hiërarchische RLS: de situatie waarbij een manager niet alleen zijn directe medewerkers mag zien, maar ook alle medewerkers die indirect onder hem vallen. Een afdelingshoofd met drie teamleiders onder zich moet het volledige plaatje van zijn afdeling kunnen zien. Die teamleiders mogen op hun beurt alleen hun eigen team zien. En de HR-directeur ziet alles.
Dit artikel legt uit hoe die hiërarchie technisch werkt in Power BI, welke DAX-patronen je nodig hebt, wat de valkuilen zijn op een organisatietabel met een Manager_ID-kolom, en wanneer het misgaat.
Wat is hiërarchische RLS en wat maakt het anders
Gewone RLS werkt statisch: gebruiker A zit in rol 'Afdeling Noord', en een tabelfilter zorgt dat hij alleen rijen met Org_Eenheid = 'Noord' ziet. Simpel. Maar in een organisatiehiërarchie is de relatie niet vlak — het is een boom. Elke medewerker heeft een Manager_ID die verwijst naar een andere rij in dezelfde tabel.
Hiërarchische RLS vergt dat je op querytijd uitrekent welke medewerkers onder de ingelogde gebruiker vallen — niet alleen op het eerste niveau, maar over alle niveaus. Dat is fundamenteel anders dan een statisch rij-filter, want het aantal niveaus varieert per organisatie en per manager.
In mid-market organisaties (250–2.000 FTE) zie je doorgaans drie tot vijf niveaus: directeur → afdelingsmanager → teamleider → medewerker, soms met nog een tussenlaag projectlead of senior. Iedere laag heeft andere zichtrechten op verzuimdata, FTE-bezetting en formatieplannen.
Er zijn drie gangbare architectuurkeuzen voor hiërarchische RLS in Power BI:
- Bridge-tabel aanpak: een vooraf berekende tabel met manager-medewerker-paren over alle hiërarchieniveaus (de meest performante optie).
- Recursieve DAX met PATH(): gebruik de native
PATH-functie om de hiërarchie on-the-fly te doorlopen binnen een berekende kolom. - Meerdere afzonderlijke rollen: één rol per org-eenheid, handmatig beheerd. Werkt alleen bij een stabiele, kleine hiërarchie en schaalt niet.
De bridge-tabel aanpak is vrijwel altijd de juiste keuze bij meer dan 500 medewerkers of bij een hiërarchie die regelmatig wijzigt (reorganisaties, nieuwe managers). De PATH-aanpak is eenvoudiger op te zetten maar vertraagt bij diepe hiërarchieën. De derde optie — handmatige rollen — is een anti-pattern: bij elke reorganisatie moet iemand in Power BI Desktop rollen aanpassen en heruitrollen, wat een deployment pipeline verstoort en fouten uitlokt.
Hoe het onder de motorkap werkt: PATH() en de bridge-tabel
De PATH()-functie uitgelegd
PATH() is een DAX-functie die een parent-child relatie doorloopt en de volledige keten teruggeeft als tekst, gescheiden door een pipe-teken. Gegeven een medewerker met ID 42, wiens manager ID 17 is, wiens manager ID 5 is, geeft PATH terug: 5|17|42. Dat is de complete lijn van de top van de hiërarchie naar beneden.
Je voegt een berekende kolom toe aan je medewerkertabel:
Hiërarchie_pad =
PATH(
Medewerkers[Medewerker_ID],
Medewerkers[Manager_ID]
)
Met PATHCONTAINS() kun je vervolgens in een RLS-filter checken of de ingelogde gebruiker ergens in het pad van een medewerker voorkomt:
-- RLS-filter op de tabel 'Medewerkers'
PATHCONTAINS(
Medewerkers[Hiërarchie_pad],
LOOKUPVALUE(
Medewerkers[Medewerker_ID],
Medewerkers[Email],
USERPRINCIPALNAME()
)
)
Dit werkt, maar heeft een serieuze beperking: bij elke rapportquery wordt PATHCONTAINS geëvalueerd over alle rijen. Bij 1.500 medewerkers is dat nog hanteerbaar. Bij grotere datasets, of wanneer de medewerkertabel wordt gecombineerd met een verzuimtabel van 100.000 rijen, loopt de query-tijd snel op.
De bridge-tabel aanpak: sneller en robuuster
Een betere aanpak is een aparte Manager_Medewerker bridge-tabel die vooraf alle manager-medewerker-combinaties berekent over alle hiërarchieniveaus. Die tabel maak je in Power Query (M) of upstream in je silver-laag.
In Power Query kun je dit doen met een recursieve functie. Hieronder een vereenvoudigd M-voorbeeld dat werkt op een bronquery genaamd Medewerkers_bron met de kolommen Medewerker_ID en Manager_ID:
let
Bron = Medewerkers_bron,
// Stap 1: maak een lijst van alle manager-medewerker directe relaties
DirecteParen = Table.SelectColumns(Bron, {"Manager_ID", "Medewerker_ID"}),
// Stap 2: definieer een recursieve functie
GetSubordinates = (ManagerID as number) as table =>
let
Directen = Table.SelectRows(DirecteParen,
each [Manager_ID] = ManagerID),
DirecteIDs = Table.Column(Directen, "Medewerker_ID"),
Recursief = List.Transform(
DirecteIDs,
each GetSubordinates(_)
),
AlleSubordinaten = Table.Combine(
List.Combine({{Directen}, Recursief})
)
in
AlleSubordinaten,
// Stap 3: voor elke manager de volledige subordinattenlijst ophalen
AlleManagers = List.Distinct(Table.Column(DirecteParen, "Manager_ID")),
BridgeRows = List.Transform(
AlleManagers,
each Table.AddColumn(
GetSubordinates(_),
"Root_Manager_ID",
each _
)
),
BridgeTabel = Table.Combine(BridgeRows)
in
BridgeTabel
Het resultaat is een tabel met twee kolommen: Root_Manager_ID en Medewerker_ID. Elke combinatie manager-medewerker over alle niveaus staat erin. Een afdelingshoofd met ID 5 heeft dan rijen voor alle 47 medewerkers die direct of indirect onder hem vallen.
De RLS-filter wordt dan simpel en razendsnel:
-- RLS-filter op de bridge-tabel 'Manager_Medewerker'
Manager_Medewerker[Root_Manager_ID] =
LOOKUPVALUE(
Medewerkers[Medewerker_ID],
Medewerkers[Email],
USERPRINCIPALNAME()
)
Via de relatie van de bridge-tabel naar de medewerkertabel wordt het filter doorgezet naar alle gerelateerde tabellen: verzuimregistraties, FTE-snapshots, formatietabellen. De ingelogde manager ziet precies zijn eigen boom — niet meer, niet minder.
Een werkend voorbeeld op een HR-dataset
Stel je hebt de volgende tabellen in je semantisch model:
- Medewerkers: Medewerker_ID, Naam, Email, Manager_ID, Org_Eenheid, FTE, Dienstverband_start, Dienstverband_einde
- Verzuim: Verzuim_ID, Medewerker_ID, Verzuim_start, Verzuim_einde, Verzuim_uren, Verzuimtype
- Formatie: Org_Eenheid, Peildatum, Formatie_FTE, Bezetting_FTE
- Manager_Medewerker (bridge): Root_Manager_ID, Medewerker_ID
De relaties in het model zien er zo uit:
| Van | Naar | Kardinaliteit | Richting |
|---|---|---|---|
| Manager_Medewerker[Medewerker_ID] | Medewerkers[Medewerker_ID] | Many-to-one | Eenrichtings (→ Medewerkers) |
| Medewerkers[Medewerker_ID] | Verzuim[Medewerker_ID] | One-to-many | Eenrichtings (→ Verzuim) |
| Medewerkers[Org_Eenheid] | Formatie[Org_Eenheid] | Many-to-many | Beide richtingen |
De RLS-rol heet 'Manager_zichtrecht' en bevat één filter op de bridge-tabel. Via de relatie manager_medewerker → medewerkers wordt het filter doorgezet naar de verzuimtabel. Geen extra filters op de verzuimtabel nodig — de modelstructuur doet het werk.
Wil je testen of de rol correct werkt zonder elke keer een andere gebruiker te simuleren? Gebruik de 'View as' functie in Power BI Desktop: Modelling → View as → selecteer 'Manager_zichtrecht' + vul een specifiek e-mailadres in. Je ziet dan exact wat die manager ziet. Dit is de enige betrouwbare manier om te valideren — tabellen met tientallen medewerkers maken handmatige controle al snel onmogelijk.
Hoe je de HR-directeur en HR-controller apart behandelt
De HR-directeur en HR-controller mogen doorgaans alles zien. Je hebt twee opties:
- Maak een aparte rol 'HR_Volledig_Toegang' met een filter die altijd
TRUE()retourneert. Voordeel: expliciet, auditeerbaar. - Wijs geen enkele RLS-rol toe. In Power BI Service ziet een gebruiker zonder RLS-rol alle data — maar dit is gevaarlijk als iemand per ongeluk buiten de verwachte rollen valt.
Optie 1 verdient de voorkeur. Elke gebruiker zit bewust in een rol. Dat maakt het verwerkingsregister dat je bijhoudt voor de AVG-compliance ook eenvoudiger: per rol kun je vastleggen welke medewerkerscategorie toegang heeft tot welke persoonsgegevens. Zie ook de AVG-checklist HR voor de 12 punten die elk HR Power BI-model moet doorlopen.
Edge cases en valkuilen
Managers zonder medewerkers
Een manager die net een team heeft overgenomen maar nog geen medewerkers in het systeem heeft, staat in AFAS of Visma als manager geregistreerd maar heeft nul subordinaatparen in de bridge-tabel. De RLS-filter retourneert dan nul rijen — de manager ziet een leeg rapport. Dit is technisch correct maar operationeel verwarrend. Bouw een fallback in: voeg altijd een rij toe in de bridge-tabel met Root_Manager_ID = Medewerker_ID (elke manager ziet tenminste zichzelf), of toon een informatieve melding in het rapport bij een leeg resultaat.
Medewerkers zonder manager
In elk HR-systeem zijn er medewerkers zonder Manager_ID — de CEO, externe inhuur die is ingeladen zonder complete structuurdata, of onboarding-records in aanmaak. Als je PATH() gebruikt, gooit de functie een fout op een NULL in Manager_ID. Vul lege Manager_IDs altijd op met de Medewerker_ID zelf (de medewerker is zijn eigen root), of filter deze records eerder uit in de silver-laag.
-- In Power Query, vóór PATH() berekend wordt:
Manager_ID_clean = if [Manager_ID] = null then [Medewerker_ID] else [Manager_ID]
Circulaire referenties
Het klinkt onwaarschijnlijk, maar het komt voor: medewerker A is manager van medewerker B, en B is ergens in een oud importbestand als manager van A geregistreerd. PATH() loopt dan in een oneindige lus en crasht de refresh. Valideer de brondata in de silver-laag op cyclische afhankelijkheden vóórdat de bridge-tabel wordt opgebouwd. Een simpele check: zoek records waar Medewerker_ID gelijk is aan een Manager_ID van een medewerker die al in de opwaartse keten zit.
Historische hiërarchiewijzigingen (SCD2)
Bij type-2 historiek (SCD2) — waarbij elke organisatiewijziging een nieuwe versie van de medewerkerrij aanmaakt met geldigheidsperiode — bestaat de hiërarchie niet als één snapshot maar als een tijdlijn. Dit heeft directe gevolgen voor de bridge-tabel: die moet dan ook tijdgebonden zijn, of je bouwt separate bridges per peildatum.
Nmbrs en AFAS leveren allebei historische mutaties. Als je verzuim wilt toewijzen aan de manager die de medewerker had op het moment van de ziekmelding, en niet de huidige manager, dan werkt een statische bridge-tabel niet. Dit is een architectuurvraagstuk dat al in de silver-laag opgelost moet worden. Een goede inrichting van de silver-laag — zie de silver-laag checklist voor data engineers — voorkomt dat je dit soort historiekproblemen op semantisch modelniveau probeert op te lossen.
E-mailadressen die niet overeenkomen
USERPRINCIPALNAME() retourneert het Azure AD-e-mailadres van de ingelogde gebruiker. Als de kolom Medewerkers[Email] gevuld is vanuit AFAS of Visma en de domeinnaam afwijkt (bijv. j.jansen@bedrijf.nl in AFAS versus jan.jansen@bedrijf.onmicrosoft.com in Azure AD), matcht de lookup niet en ziet de manager niets. Controleer bij implementatie altijd of de e-mailadressen in het HR-bronsysteem overeenkomen met de UPN in Azure AD. Veelvoorkomend bij organisaties die recent zijn gefuseerd of een domeinnaamwijziging hebben doorgevoerd.
OLS als aanvulling op RLS: kolommen verbergen
RLS filtert rijen. Maar soms wil je ook bepaalde kolommen afschermen — denk aan salarisschaal, BSN of medische diagnose-informatie die in de verzuimtabel staat. Daarvoor gebruik je OLS (Object Level Security).
OLS is niet beschikbaar via de Power BI Desktop RLS-interface. Je configureert het via Tabular Editor (versie 2 of 3) of via de Tabular Object Model API. In Tabular Editor stel je per rol in of een kolom Read, None (verborgen) of Default is. Een gebruiker die een kolom met status None probeert te gebruiken in een visual, krijgt een foutmelding — de kolom bestaat niet voor hem.
Praktisch gebruik in HR: de kolom Verzuimtype kan categorieën bevatten als 'frequent kort verzuim', 'langdurig verzuim' of 'zwangerschap'. Teamleiders mogen het totaal aantal verzuimdagen zien, maar niet het type. De HR-controller en HR-directeur zien alles. Met OLS kun je dat onderscheid maken zonder extra berekende kolommen of complexe DAX-filters.
Let op: OLS werkt op kolom- en tabelniveau, niet op celniveau. Je kunt niet zeggen 'verberg de waarde in rij X van kolom Y'. Als je celniveau-afscherming nodig hebt, zit je in een complexiteit die Power BI niet native ondersteunt en moet je naar een andere architectuur (bijv. Fabric met Row-level encryption of een downstream API).
Performance- en governance-implicaties
Performance: bridge-tabel vs. PATH in productie
De PATH/PATHCONTAINS-aanpak evalueert de hiërarchiefunctie per rij bij elke query. Op een medewerkertabel van 1.000 rijen en een verzuimtabel van 80.000 rijen betekent dat bij elke rapportpaginalaad een full-scan van beide tabellen. Met de bridge-tabel reduceert de RLS-filter tot een eenvoudige integer-vergelijking op een vooraf berekende tabel — dat is een factor 10 tot 50 sneller op typische HR-datasets in de mid-market.
Concreet: in een model met 1.500 medewerkers en 120.000 verzuimrijen zag de PATH-aanpak query-tijden van 4–8 seconden bij een manager met een groot team. Na migratie naar een bridge-tabel: 0,3–0,6 seconden. Hetzelfde model, dezelfde hardware, alleen de RLS-architectuur gewijzigd.
Governance: wie beheert de rollen?
RLS-rollen worden aangemaakt in het semantisch model. Dat model moet bij elke functiewijziging (nieuwe manager, reorganisatie) correct worden bijgewerkt. Als de bridge-tabel automatisch wordt ververst vanuit het HR-bronsysteem — wat de standaard zou moeten zijn — is het roltoewijzingsproces grotendeels zelfonderhoudend. De enige handmatige stap is het toewijzen van de juiste Azure AD-gebruikers aan de juiste Power BI-rollen in de Power BI Service.
Documenteer welke rollen bestaan, welke gebruikers erin zitten, en welke dataklassen zij kunnen zien. Dat is niet optioneel — het is onderdeel van het verwerkingsregister dat de AVG vereist. De DPO moet kunnen aantonen dat toegang tot persoonsgegevens van medewerkers beperkt is tot degenen met een legitiem belang. Een niet-gedocumenteerde RLS-configuratie in Power BI is een AVG-risico, ook al werkt de technische beveiliging correct.
Zie ook de uitgebreidere gids over Row Level Security in Power BI: complete implementatiegids voor enterprise beveiliging voor het bredere plaatje van RLS-beheer, inclusief het gebruik van security groups in Azure AD als basis voor roltoewijzing.
Power BI Desktop versus Service: testbaar maken
In Power BI Desktop test je RLS met de 'View as'-functie. In de Power BI Service kun je ook 'Test as role' gebruiken vanuit de dataset-instellingen. Het verschil: in Desktop test je altijd met de lokale versie van het model; in Service test je de gepubliceerde versie. Na een publicatie kan het zijn dat een berekende kolom met PATH() anders gedraagt door een ander refreshmoment. Test na elke grote wijziging altijd in Service, niet alleen lokaal.
Werk je met deployment pipelines? Zorg dat je in de TEST-omgeving ook RLS-rollen valideert met testgebruikers die representatief zijn voor alle managementniveaus. Een teamleider, een afdelingshoofd en de HR-directeur — elk met hun eigen UPN — als testaccount. Automatiseer die validatie niet weg; het is juist de menselijke controle die fouten in hiërarchieopbouw opvangt.
Fabric F-SKU en RLS: wat verandert er
Op een Microsoft Fabric F-SKU (F2 en hoger) draait het semantisch model op dezelfde engine als in Power BI Premium. RLS werkt identiek. Het verschil zit in de refresh-capaciteit: op een F2 heb je parallelle refresh beschikbaar, waardoor de bridge-tabel en de medewerkertabel tegelijk ververst kunnen worden. Op Power BI Pro (gedeelde capaciteit) is parallelle refresh niet beschikbaar en moeten tabellen sequentieel worden ververst — bij een complexe bridge-tabel kan dat de totale refreshtijd verlengen.
Op F4 en hoger is ook XMLA write beschikbaar, wat Tabular Editor nodig heeft om OLS te configureren via de API. Als je OLS wilt inzetten voor kolomafscherming (bijv. salarisdata verbergen voor teamleiders), heb je minimaal een F4-capaciteit of een Power BI Premium Per User-licentie nodig.
Vuistregels
Concrete heuristieken die je morgen op je HR-model kunt toepassen:
- Gebruik altijd een bridge-tabel bij meer dan 300 medewerkers of meer dan drie hiërarchieniveaus. PATH/PATHCONTAINS is prima voor prototypes, niet voor productie.
- Bouw de bridge-tabel in de silver-laag, niet in Power Query van het semantisch model. Zo is de hiërarchieopbouw testbaar, herbruikbaar en onafhankelijk van Power BI.
- Elke gebruiker zit in een expliciete rol. Nooit vertrouwen op 'geen rol = alles zien'. Maak een aparte rol 'HR_Volledig_Toegang' voor de HR-directeur en HR-controller.
- Valideer e-mailadressen vóór go-live. Controleer dat de UPN in Azure AD matcht met het e-mailadres in AFAS, Visma of Nmbrs. Dit is de nummer-één reden waarom RLS 'plots niet werkt' na implementatie.
- Behandel NULL Manager_IDs in de brondata voordat ze de bridge-tabel bereiken. Vul ze op met de Medewerker_ID zelf.
- Gebruik OLS voor gevoelige kolommen (salarisschaal, verzuimtype, BSN) en documenteer dit in het verwerkingsregister voor de DPO.
- Test in de Power BI Service na elke publicatie, met echte testaccounts voor elk managementniveau — niet alleen lokaal in Desktop.
- Bij SCD2-historiek: los de tijdgebonden hiërarchietoewijzing op in de silver-laag, niet met DAX-filters in het semantisch model. Zie de 7 keuzes voor een schaalbaar HR semantic model voor hoe je historiek correct modelleert.
- Houd de bridge-tabel klein: bevat uitsluitend ID-paren, geen persoonsgegevens. Zo is hij ook van toepassing op alle gerelateerde feitentabellen zonder AVG-complicaties.
- Documenteer elke rol met: welke gebruikers, welke dataklassen zichtbaar, datum van laatste wijziging. Niet als formaliteit — als bewijs richting de DPO dat toegangsbeheer geregeld is.
Hiërarchische RLS is geen eenmalige configuratie. Bij elke reorganisatie, elke managementwisseling en elke uitbreiding van het HR-datamodel moet je controleren of de beveiligingslogica nog klopt. Bouw het dus zo in dat het met de organisatie meebeweegt — automatisch, vanuit het bronsysteem, zonder handmatig ingrijpen in het semantisch model. Dat is het verschil tussen RLS als technisch trucje en RLS als structureel onderdeel van een governance framework.