Business Professionals
Power BI | Power Pivot | Power Query | DAX
Cloud Flows | RPA | AI Builder | Copilot
60+ Formulas | Data Stories | Advanced Reporting & Modeling
VB Programming | Report Automation |
MS-Office Automation
Techno-Business Professionals
Power BI | Power Query | Advanced DAX | SQL - Query &
Programming
Microsoft Fabric | Power BI | Power Query | Advanced DAX |
SQL - Query & Programming
Power BI | Power Apps | Power Automate | Copilot Studio | Power Pages | Dataverse
Microsoft Power Apps | Microsoft Power Automate
Power BI | Adv. DAX | SQL (Query & Programming) |
VBA | Python | Web Scrapping | API Integration
Power BI | Power Apps | Power Automate |
SQL (Query & Programming)
Power BI | Adv. DAX | Power Apps | Power Automate |
SQL (Query & Programming) | VBA | Python | Web Scrapping | API Integration
Power Apps | Power Automate | SQL | VBA | Python |
Web Scraping | RPA | API Integration
Technology Professionals
Power BI | DAX | SQL | ETL with SSIS | SSAS | VBA | Python
Power BI | SQL | Azure Data Lake | Synapse Analytics |
Data Factory | Databricks | Power Apps | Power Automate |
Azure Analysis Services
Microsoft Fabric | Power BI | SQL | Lakehouse |
Data Factory (Pipelines) | Dataflows Gen2 | KQL | Delta Tables | Power Apps | Power Automate
Power BI | Power Apps | Power Automate | SQL | VBA | Python | API Integration
Power BI | Advanced DAX | Databricks | SQL | Lakehouse Architecture
Business Professionals
Power BI | Power Pivot | Power Query | DAX
Cloud Flows | RPA | AI Builder | Copilot
60+ Formulas | Data Stories | Advanced Reporting & Modeling
VB Programming | Report Automation |
MS-Office Automation
Techno-Business Professionals
Power BI | Power Query | Advanced DAX | SQL - Query &
Programming
Microsoft Fabric | Power BI | Power Query | Advanced DAX |
SQL - Query & Programming
Power BI | Power Apps | Power Automate | Copilot Studio | Power Pages | Dataverse
Microsoft Power Apps | Microsoft Power Automate
Power BI | Adv. DAX | SQL (Query & Programming) |
VBA | Web Scrapping | API Integration
Power BI | Power Apps | Power Automate |
SQL (Query & Programming)
Power BI | Adv. DAX | Power Apps | Power Automate |
SQL (Query & Programming) | VBA | Web Scrapping | API Integration
Power Apps | Power Automate | SQL | VBA |
Web Scraping | RPA | API Integration
Technology Professionals
Power BI | DAX | SQL | ETL with SSIS | SSAS | VBA
Power BI | SQL | Azure Data Lake | Synapse Analytics |
Data Factory | Azure Analysis Services
Microsoft Fabric | Power BI | SQL | Lakehouse |
Data Factory (Pipelines) | Dataflows Gen2 | KQL | Delta Tables
Power BI | Power Apps | Power Automate | SQL | VBA | API Integration
Power BI | Advanced DAX | Databricks | SQL | Lakehouse Architecture
You can design beautiful row-level security (RLS) in MS-SQL and still end up with a mess once Power BI or Fabric semantic models hit it. This article walks through an RLS design that survives real-world reporting: works with DirectQuery and import, plays nicely with role-based access, and stays maintainable as your model grows.
If you want to go beyond patterns and build a full stack where SQL and Power BI are aligned end-to-end, it's worth investing in strong Power BI + SQL foundations so these patterns become second nature.
When we say RLS that works well with Power BI / Fabric semantic models, we want:
You don’t need every possible RLS feature. You need a pattern you can repeat.
Forget ad-hoc WHERE clauses. The pattern that scales is:
Classic: sales data, restricted by region.
CREATE SCHEMA sec;
GO
CREATE TABLE sec.UserRegion
(
UserPrincipalName nvarchar(256) NOT NULL,
RegionKey int NOT NULL,
CONSTRAINT PK_UserRegion PRIMARY KEY (UserPrincipalName, RegionKey)
);
CREATE TABLE dbo.Region
(
RegionKey int NOT NULL CONSTRAINT PK_Region PRIMARY KEY,
RegionCode nvarchar(50) NOT NULL,
RegionName nvarchar(200) NOT NULL
);
CREATE TABLE dbo.Sales
(
SalesId int IDENTITY(1,1) PRIMARY KEY,
OrderDate date NOT NULL,
CustomerId int NOT NULL,
RegionKey int NOT NULL,
Amount decimal(18,2) NOT NULL,
CONSTRAINT FK_Sales_Region FOREIGN KEY (RegionKey) REFERENCES dbo.Region(RegionKey)
);
Key points:
RegionKey is the bridge between RLS and the fact table.UserPrincipalName will match the UPN that Power BI passes through when using SSO.Use an inline TVF so the optimizer can do its job.
CREATE FUNCTION sec.fnUserRegionPredicate(@upn nvarchar(256))
RETURNS TABLE
WITH SCHEMABINDING
AS
RETURN
(
SELECT 1 AS Allowed
FROM sec.UserRegion ur
WHERE ur.UserPrincipalName = @upn
);
GO
CREATE SECURITY POLICY sec.SalesRegionRLS
ADD FILTER PREDICATE sec.fnSalesRegionFilter(UserPrincipalName())
ON dbo.Sales
WITH (STATE = ON);
GO
We need a wrapper function that ties UPN to the Sales table:
CREATE FUNCTION sec.fnSalesRegionFilter(@upn nvarchar(256))
RETURNS TABLE
WITH SCHEMABINDING
AS
RETURN
(
SELECT 1 AS Allowed
FROM dbo.Sales s
JOIN sec.UserRegion ur
ON ur.RegionKey = s.RegionKey
AND ur.UserPrincipalName = @upn
);
GO
This is the core RLS: every query against dbo.Sales is transparently filtered by the caller’s regions.
You have three options:
For enterprise setups with Fabric or central semantic models, option 1 is usually the cleanest:
Use Power BI roles only when:
For RLS to apply per user, Power BI must connect using:
If the model uses a single technical account, SQL sees that account for every query and RLS becomes useless.
Quick checks:
SELECT ORIGINAL_LOGIN() AS OriginalLogin,
SUSER_SNAME() AS CurrentLogin,
SESSION_CONTEXT(N'AzureADTokenUPN') AS UPN;
UPN matches the email address you store in sec.UserRegion.Fabric semantic models are just Power BI datasets with a new name, but the same rules apply.
Key design decisions:
Common dimensions to secure by:
Pattern:
Example: secure by CompanyKey on DimCompany, not directly on every fact.
CREATE TABLE dbo.DimCompany
(
CompanyKey int NOT NULL PRIMARY KEY,
CompanyCode nvarchar(50) NOT NULL,
CompanyName nvarchar(200) NOT NULL
);
CREATE TABLE sec.UserCompany
(
UserPrincipalName nvarchar(256) NOT NULL,
CompanyKey int NOT NULL,
CONSTRAINT PK_UserCompany PRIMARY KEY (UserPrincipalName, CompanyKey)
);
RLS function:
CREATE FUNCTION sec.fnCompanyFilter(@upn nvarchar(256))
RETURNS TABLE
WITH SCHEMABINDING
AS
RETURN
(
SELECT 1 AS Allowed
FROM dbo.DimCompany c
JOIN sec.UserCompany uc
ON uc.CompanyKey = c.CompanyKey
AND uc.UserPrincipalName = @upn
);
GO
CREATE SECURITY POLICY sec.CompanyRLS
ADD FILTER PREDICATE sec.fnCompanyFilter(UserPrincipalName())
ON dbo.DimCompany
WITH (STATE = ON);
GO
In the semantic model, relate DimCompany to all relevant fact tables. RLS on DimCompany will naturally filter facts.
Applying RLS directly on a massive fact table can hurt performance, especially in DirectQuery.
Prefer:
DimCompany, DimRegion).Only put RLS on facts when:
Hardcoding individual users into sec.UserRegion doesn’t scale.
Better patterns:
AD group-based security
Role-based security
Sales_Manager, Sales_Rep.Example with roles:
CREATE TABLE sec.Role
(
RoleId int IDENTITY(1,1) PRIMARY KEY,
RoleName nvarchar(100) NOT NULL UNIQUE
);
CREATE TABLE sec.UserRole
(
UserPrincipalName nvarchar(256) NOT NULL,
RoleId int NOT NULL,
CONSTRAINT PK_UserRole PRIMARY KEY (UserPrincipalName, RoleId),
CONSTRAINT FK_UserRole_Role FOREIGN KEY (RoleId) REFERENCES sec.Role(RoleId)
);
CREATE TABLE sec.RoleRegion
(
RoleId int NOT NULL,
RegionKey int NOT NULL,
CONSTRAINT PK_RoleRegion PRIMARY KEY (RoleId, RegionKey),
CONSTRAINT FK_RoleRegion_Role FOREIGN KEY (RoleId) REFERENCES sec.Role(RoleId)
);
Predicate function now joins through roles:
CREATE FUNCTION sec.fnSalesRegionFilter(@upn nvarchar(256))
RETURNS TABLE
WITH SCHEMABINDING
AS
RETURN
(
SELECT DISTINCT 1 AS Allowed
FROM dbo.Sales s
JOIN sec.RoleRegion rr
ON rr.RegionKey = s.RegionKey
JOIN sec.UserRole ur
ON ur.RoleId = rr.RoleId
AND ur.UserPrincipalName = @upn
);
GO
Now changing access becomes:
No change to RLS logic itself.
Don’t trust RLS until you’ve tested it from both sides: SQL and Power BI.
Use EXECUTE AS USER to simulate a login.
EXECUTE AS USER = 'user1@contoso.com';
SELECT TOP 10 * FROM dbo.Sales;
REVERT;
Check:
If you must debug in DirectQuery:
EXECUTE AS).RLS can be cheap or expensive depending on design.
Keep it cheap by:
Example indexes:
CREATE INDEX IX_UserRegion_User ON sec.UserRegion(UserPrincipalName, RegionKey);
CREATE INDEX IX_UserCompany_User ON sec.UserCompany(UserPrincipalName, CompanyKey);
For DirectQuery:
If you also define RLS roles in Power BI:
This can be useful for:
But beware:
Whenever possible, keep data access rules in SQL and UX-level rules in DAX (e.g., hiding measures), not RLS.
Before you build your next semantic model, decide one canonical security axis (region, company, or customer), model it as a dimension with a clean key, and implement SQL RLS only on that dimension using an inline TVF and a security policy. Then point Power BI or Fabric at that model with SSO enabled. You’ll get consistent, auditable row-level security that behaves the same way in every report, without fighting conflicting rules in multiple layers.
MS-SQL
New
Next Batches Now Live
Power BI
SQL
Power Apps
Power Automate
Microsoft Fabrics
Azure Data Engineering