A Power BI Sales Dashboard is the single most requested BI project I get — and for good reason. It gives sales managers, CFOs, and operations teams a real-time view of revenue, margins, and team performance without waiting for a manual Excel update.
In this guide, I'll walk you through building a production-ready sales dashboard from scratch — the same approach I use on client projects. By the end, you'll have a complete dashboard connected to SQL Server, with calculated DAX measures, drill-through capabilities, and published to Power BI Service.
You'll need Power BI Desktop (free download from Microsoft), a basic understanding of Excel, and optionally SQL Server or a sample dataset. I'll provide sample data links in each section.
1. Planning Your Dashboard
Before opening Power BI, spend 15 minutes answering these questions. They'll save you hours of rework later:
- Who is the primary user? Sales Director, Regional Manager, or Sales Rep — each needs different detail levels.
- What decisions does this dashboard need to support? Revenue targets, rep performance, product mix — be specific.
- What are the top 5 KPIs? Total Revenue, Gross Margin %, Top Products, Sales by Region, MTD vs Target.
- What filters will users need? Date range, region, product category, sales rep.
- What data sources are involved? ERP, CRM, Excel file, SQL database?
For this tutorial, we're building for a Sales Director who needs to track revenue, margins, and rep performance across 4 regions. Our KPIs are: Total Revenue, Gross Margin %, Sales vs Target, Top 10 Products, and MTD Revenue.
2. Connecting Your Data Sources
Go to Home → Get Data → SQL Server. Enter your server name and credentials. For this tutorial, we'll use three tables from a Sales database:
dbo.FactSales— transaction-level sales recordsdbo.DimProduct— product master with category, cost, and list pricedbo.DimDate— a proper date dimension table
Never use auto date/time in Power BI for production dashboards. A proper DimDate table gives you full control over time intelligence and dramatically improves DAX performance.
Creating the Date Dimension in SQL
If you don't have a date dimension, here's the SQL to generate one:
-- Create a complete Date Dimension table
CREATE TABLE dbo.DimDate (
DateKey INT PRIMARY KEY,
FullDate DATE NOT NULL,
Year INT NOT NULL,
Quarter INT NOT NULL,
Month INT NOT NULL,
MonthName VARCHAR(20) NOT NULL,
WeekNumber INT NOT NULL,
DayOfWeek INT NOT NULL,
IsWeekend BIT NOT NULL,
FiscalYear INT NOT NULL,
FiscalQuarter INT NOT NULL
);
-- Populate with dates from 2020 to 2030
DECLARE @StartDate DATE = '2020-01-01';
DECLARE @EndDate DATE = '2030-12-31';
DECLARE @Date DATE = @StartDate;
WHILE @Date <= @EndDate
BEGIN
INSERT INTO dbo.DimDate VALUES (
CONVERT(INT, FORMAT(@Date, 'yyyyMMdd')),
@Date,
YEAR(@Date),
DATEPART(QUARTER, @Date),
MONTH(@Date),
DATENAME(MONTH, @Date),
DATEPART(WEEK, @Date),
DATEPART(WEEKDAY, @Date),
CASE WHEN DATEPART(WEEKDAY, @Date) IN (1,7) THEN 1 ELSE 0 END,
CASE WHEN MONTH(@Date) >= 4 THEN YEAR(@Date) + 1 ELSE YEAR(@Date) END,
CASE WHEN MONTH(@Date) IN (4,5,6) THEN 1
WHEN MONTH(@Date) IN (7,8,9) THEN 2
WHEN MONTH(@Date) IN (10,11,12) THEN 3
ELSE 4 END
);
SET @Date = DATEADD(DAY, 1, @Date);
END
3. Building the Data Model
After loading your tables, go to Model View. You should create a Star Schema — one fact table in the centre, dimension tables around it. This is the foundation of every well-performing Power BI report.
- Connect
FactSales.DateKey→DimDate.DateKey(Many-to-One) - Connect
FactSales.ProductKey→DimProduct.ProductKey(Many-to-One) - Connect
FactSales.CustomerKey→DimCustomer.CustomerKey(Many-to-One)
If Power BI suggests a many-to-many relationship, it's a red flag — usually means your data model has duplicates or you need a bridge table. Fix the data, don't accept the warning.
4. Writing the Core DAX Measures
All measures go into a dedicated Measures Table (right-click → Enter Data → create an empty table called "_Measures"). This keeps your model clean.
Revenue Measures
// ── Core Revenue Measures ──────────────────────────
Total Revenue =
SUMX(
FactSales,
FactSales[Quantity] * FactSales[UnitPrice]
)
Total COGS =
SUMX(
FactSales,
FactSales[Quantity] * RELATED(DimProduct[UnitCost])
)
Gross Profit =
[Total Revenue] - [Total COGS]
Gross Margin % =
DIVIDE(
[Gross Profit],
[Total Revenue],
0 -- return 0 if Revenue is blank (avoid divide-by-zero)
)
Time Intelligence Measures
Time intelligence is where Power BI's DAX really shines. These measures use your DimDate table, so mark it as a Date Table first (right-click → Mark as date table).
// ── Time Intelligence ──────────────────────────────
Revenue MTD =
TOTALMTD([Total Revenue], DimDate[FullDate])
Revenue YTD =
TOTALYTD([Total Revenue], DimDate[FullDate])
Revenue LY =
CALCULATE(
[Total Revenue],
SAMEPERIODLASTYEAR(DimDate[FullDate])
)
Revenue YoY % =
DIVIDE(
[Total Revenue] - [Revenue LY],
[Revenue LY],
BLANK()
)
Revenue vs Target =
[Total Revenue] - [Sales Target]
Revenue vs Target % =
DIVIDE([Revenue vs Target], [Sales Target], 0)
Never use the / operator for division in DAX. DIVIDE(numerator, denominator, alternateResult) handles blank and zero denominators gracefully — preventing errors in your dashboard.
Ranking Measures
// ── Rankings ───────────────────────────────────────
Product Rank by Revenue =
RANKX(
ALLSELECTED(DimProduct[ProductName]),
[Total Revenue],
,
DESC,
DENSE
)
Top N Products =
VAR TopN = SELECTEDVALUE(TopNSelector[N], 10)
RETURN
IF(
[Product Rank by Revenue] <= TopN,
[Total Revenue],
BLANK()
)
5. Designing the Visuals
Now the creative part. A great dashboard follows a clear reading hierarchy:
| Zone | Content | Visuals |
|---|---|---|
| Top strip | KPI Cards | Card, KPI visual |
| Left main | Revenue trend over time | Line/Area chart |
| Right main | Revenue by Region / Rep | Bar chart, Map |
| Bottom | Top Products, Detail table | Table, Matrix |
| Right sidebar | Filters | Slicers |
KPI Card Setup
For each KPI Card, use the New Card visual (not the legacy Card). Add your measure to the "Values" field, then configure:
- Callout value: Your main measure (e.g.,
[Total Revenue]) - Reference label:
[Revenue YoY %]with conditional formatting (green ↑ / red ↓) - Category label: Plain text description
In card visuals, set the reference value font colour using a DAX measure: KPI Colour = IF([Revenue YoY %] >= 0, "#10B981", "#EF4444") — then apply it as a dynamic colour in Format → Callout Value → Font color → Field value.
6. Adding Row-Level Security (RLS)
If different sales reps should only see their own data, set up RLS. In Modeling → Manage Roles:
// Applied to DimSalesRep table
// USERNAME() returns the logged-in user's email in Power BI Service
[Email] = USERNAME()
Then publish the report and assign users to RLS roles in Power BI Service → Dataset Settings → Security.
7. Publishing to Power BI Service
Once your report is tested and validated:
- Go to Home → Publish — sign in with your Power BI account
- Select your target workspace (create one for the client)
- In Power BI Service, configure the Data Gateway if using SQL Server
- Set up a Scheduled Refresh — daily at 6 AM usually works well
- Create a Dashboard by pinning key visuals from the report
- Share with users or configure an App for broader distribution
If your SQL Server is on-premises (not Azure), you must install the On-premises Data Gateway on a server that's always on. Without it, scheduled refresh won't work.
8. Performance Optimisation Checklist
Before handing over to the client, run through this checklist:
- ✅ Star schema — no many-to-many, no bidirectional filters (unless necessary)
- ✅ Import mode — not DirectQuery unless you genuinely need real-time data
- ✅ Remove unused columns — every column loads into memory
- ✅ Use aggregations for very large tables (10M+ rows)
- ✅ Avoid calculated columns for things you can do in Power Query
- ✅ Use variables in DAX (
VAR/RETURN) to avoid re-evaluation - ✅ Test with Performance Analyzer (View → Performance Analyzer)