← Emberlern/The Data Diaries With Dee
How to Be a Top-Notch Data Analyst / Module 06

Power BI: from dataset to decision surface

Seven lessons, about an hour and 20 minutes. You'll build a real Power BI model from Sahara Mart's actual three-month sales data — a proper star schema, not a single flat table.

Power BI dataset Sahara Mart's real three-month sales data, built as a proper star schema — a Sales fact table plus Branches, Products and Calendar dimension tables. Import it into Power BI and build the relationships and measures yourself, exactly as this module describes. One .xlsx, five tabs, 8,908 transactions.
Lesson 6.112 minutes

The data model explained without jargon

The dataset for this module isn't one flat spreadsheet — it's four separate tables, deliberately, because that's how Power BI actually wants data structured. Understanding why is the whole foundation this module sits on.

Facts and dimensions, in plain terms
Fact table (Sales)One row per transaction. This is where the numbers live — revenue, quantity. It's the biggest table by far, and it grows every day.
Dimension tables (Branches, Products, Calendar)One row per "thing you'd group or filter by." Small, stable tables that describe the fact table's rows without repeating that description on every single transaction.

The reason this separation matters: if branch name, city, product category and price all lived directly inside the Sales table, every single transaction row would repeat "Lekki, Lagos" or "Beverages, ₦1,200" over and over — and the moment a branch is renamed, you'd need to update thousands of rows instead of one. A star schema — one fact table surrounded by small dimension tables — keeps each fact in exactly one place.

Grain matters here just as it did in SQL: one row in Sales is one transaction. Get that wrong in your head and every relationship you build afterward will misbehave in ways that are hard to trace back to the cause.

Keep this
  • A fact table holds the numbers and grows constantly; dimension tables describe things you group or filter by and stay small.
  • Keeping descriptive detail in dimension tables, not repeated on every fact row, is what makes the model maintainable as data grows.
Lesson 6.211 minutes

Relationships, and why your totals are wrong

Import all four tabs and Power BI won't automatically know that Sales.branch_id refers to Branches.branch_id — you have to build that relationship explicitly in Model view, dragging branch_id in Sales to branch_id in Branches. Skip this, or get the direction wrong, and totals silently misbehave.

The direction matters

A relationship should flow many-to-one: many Sales rows relate to one Branches row. Power BI defaults to this correctly most of the time when you drag the join — but if you ever see a warning about "many-to-many," stop and check which table actually has one row per key before proceeding, because a many-to-many relationship changes how totals filter in ways that are easy to miss until a number looks subtly wrong.

Once all three relationships (to Branches, Products, and Calendar) are built correctly, filtering your dashboard by branch, category, or date range works everywhere at once — that's the entire point of the model, and it's why this setup step, done once, saves rebuilding filter logic in every visual individually.

Keep this
  • Build every relationship explicitly in Model view — don't assume Power BI inferred it correctly from column names alone.
  • A correctly built relationship is what makes one filter (branch, date, category) apply consistently across every visual on the page.
Lesson 6.312 minutes

DAX: measures versus calculated columns

Two ways to add a calculation in Power BI, and choosing wrong is the most common beginner mistake. A calculated column computes a value for every row, stored permanently — expensive, and usually unnecessary. A measure computes on the fly, in response to whatever filter is currently applied — cheap, flexible, and almost always what you actually want.

Total Revenue, done right

Total Revenue = SUM(Sales[revenue])

This is a measure. It doesn't store a value anywhere — it recalculates instantly whenever a filter (branch, date range, category) changes, which is exactly the behaviour a dashboard needs.

A calculated column would make sense for something genuinely per-row and static — a "Price Band" label derived from unit_price, say. Almost everything you'll build in this module — totals, averages, percentages, comparisons — belongs as a measure, not a column.

Keep this
  • Default to a measure. Use a calculated column only for something genuinely fixed per row.
  • A measure recalculates live against whatever filter is applied — that's what makes a dashboard interactive rather than static.
Lesson 6.411 minutes

Time intelligence: last month, same period last year, rolling twelve

"How's this month doing compared to last month" needs the Calendar table you imported — this is exactly why a proper date dimension exists rather than relying on the raw dates buried in Sales.

A month-over-month comparison measure

Revenue Last Month = CALCULATE([Total Revenue], DATEADD(Calendar[date], -1, MONTH))

CALCULATE changes the filter context; DATEADD shifts the date filter back one month. This pattern — CALCULATE wrapping a time-shift function — is the backbone of nearly every time-intelligence measure you'll ever write.

Build the reverse comparison too: MoM Change % = DIVIDE([Total Revenue] - [Revenue Last Month], [Revenue Last Month]). DIVIDE, not a plain slash, matters here — it handles a divide-by-zero gracefully (returning blank instead of an error) for any branch or period with no prior data.

Keep this
  • Time intelligence measures need a real Calendar table — build one rather than relying on raw dates in the fact table.
  • Use DIVIDE instead of the / operator in DAX — it avoids errors when the denominator is zero.
Lesson 6.511 minutes

Designing one page a manager reads in ten seconds

Chidi has ten seconds, standing up, between meetings. A dashboard with fourteen visuals crammed onto one page fails him completely, however accurate every number on it is — the same lesson from module 2.4's pivot-table discipline, applied at dashboard scale.

A one-page ops dashboard that actually works
  1. Three or four KPI cards at the top. Total revenue, MoM change, top branch, worst branch — the headline, in the first half-second of looking.
  2. One chart that shows the trend. Revenue by day or week, so a dip or spike is visible without reading numbers.
  3. One breakdown that shows where. Revenue by branch, sorted, so the "who's dragging this down" question is answered without a follow-up.
  4. Filters at the top, not buried. Date range and branch, visible and obvious, so Chidi can self-serve a follow-up question himself.

Every visual you add after the fourth one is competing for the same ten seconds of attention as the first three. Most of them lose.

Keep this
  • Design for a ten-second glance: a few KPI cards, one trend, one breakdown, filters visible — not comprehensive coverage.
  • Every additional visual has a real cost in attention, not just screen space.
Lesson 6.611 minutes

Publishing, scheduled refresh, and row-level security basics

A dashboard that only updates when you personally open Power BI Desktop and hit refresh is a dashboard that will be stale the one week you're on leave. Publishing to the Power BI service and setting a scheduled refresh — daily, against the live source once one exists — is what turns a one-off report into a real operational tool.

Row-level security, briefly

Sahara Mart's nine branch managers should each see their own branch's numbers, not all nine — that's Row-Level Security (RLS), a rule applied at the data level so the same published report shows different rows to different viewers automatically, rather than maintaining nine separate copies of the same dashboard.

Keep this
  • Publish and schedule refresh — a dashboard that requires manual refresh will go stale the moment you're unavailable.
  • Row-Level Security lets one published report serve different audiences their own slice, instead of maintaining separate copies.
Lesson 6.712 minutes

What to do when someone says the dashboard is wrong

It will happen — a branch manager insists their branch's number on the dashboard is wrong. The wrong response is either defensive certainty ("the dashboard is correct") or immediate capitulation ("let me fix that"). The right response is verification, out loud, together.

The verification sequence
  1. Ask what number they expected, and where it came from — their own till report, a memory, a different system entirely?
  2. Check the filter state of the dashboard when they looked at it — a wrong date range or an accidentally-applied filter from a previous session is the single most common cause of "wrong" numbers.
  3. If the filters are right and the numbers still disagree, trace the dashboard's number back through the DAX measure to the raw Sales rows for that branch and period — actually run the underlying query, don't just re-eyeball the visual.
  4. Report back either "confirmed correct, here's why" or "found the actual error, here's the fix and when it'll be live" — never leave the disagreement unresolved and unspoken.
Why this matters more than getting it right the first time

Every dashboard will eventually be challenged. How you handle the challenge — calmly, systematically, without defensiveness — is what determines whether people keep trusting the dashboard afterward, more than whether this particular instance turned out to be a real bug or a misunderstanding.

Keep this
  • Verify systematically — expected number, filter state, then trace back to raw data — rather than defending or capitulating immediately.
  • Always close the loop with a clear answer, whether the dashboard was right or a real bug was found.
Before the drill

Have Power BI Desktop installed and the dataset imported — this drill is a real build, not a written exercise.

Module 6 drillabout two hours

Build the ops dashboard, defend the numbers

Using the Power BI dataset, build a single-page operations dashboard for Sahara Mart, then defend every number on it.

  1. Build the star schema: import all four tables, create the three relationships correctly.
  2. Write four measures: Total Revenue, Total Revenue Last Month, MoM Change %, and Average Basket Size (excluding VOIDs).
  3. Build the one-page dashboard from lesson 6.5's template: KPI cards, a trend chart, a branch breakdown, visible filters.
  4. Write down, for each number on the page, exactly what it counts and what it excludes — as if handing this to Chidi cold.
  5. Pick your Total Revenue KPI card and trace it back by hand to a raw SUM over the Sales table filtered the same way, confirming the two numbers match exactly.
What this drill is really testing

Whether the whole module comes together as one working thing — a correct model, measures that use CALCULATE and DIVIDE properly, a page designed for a ten-second glance, and the discipline from lesson 6.7 to actually verify a number rather than just trust that it looks right.

Before module 7

Keep this Power BI file — module 7 automates the manual report you built by hand back in module 2, using Python, and the same underlying data shows up again from a different angle.