<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Alex Majale</title>
    <description>The latest articles on DEV Community by Alex Majale (@alex_majale_d64efa6d81883).</description>
    <link>https://dev.to/alex_majale_d64efa6d81883</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F4070903%2F17fb8e54-8213-48a8-8c48-2f8a9a49e779.png</url>
      <title>DEV Community: Alex Majale</title>
      <link>https://dev.to/alex_majale_d64efa6d81883</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/alex_majale_d64efa6d81883"/>
    <language>en</language>
    <item>
      <title>From Messy Transactions to Business Insights: My JCars Power BI Project</title>
      <dc:creator>Alex Majale</dc:creator>
      <pubDate>Tue, 29 Sep 2026 13:51:48 +0000</pubDate>
      <link>https://dev.to/alex_majale_d64efa6d81883/from-messy-transactions-to-business-insights-my-jcars-power-bi-project-3lpi</link>
      <guid>https://dev.to/alex_majale_d64efa6d81883/from-messy-transactions-to-business-insights-my-jcars-power-bi-project-3lpi</guid>
      <description>&lt;p&gt;When I started working on the JCars dataset, my first instinct was to think about the dashboard.&lt;/p&gt;

&lt;p&gt;Which KPIs should I create?&lt;br&gt;
Which visualizations should I use?&lt;br&gt;
How should the report look?&lt;/p&gt;

&lt;p&gt;But I quickly realised that the dashboard was not the difficult part.&lt;/p&gt;

&lt;p&gt;The difficult part was understanding whether the data underneath it could actually be trusted.&lt;/p&gt;

&lt;p&gt;That changed how I approached the entire project.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Dataset
&lt;/h2&gt;

&lt;p&gt;The JCars dataset contained &lt;strong&gt;276 transaction records and 46 columns&lt;/strong&gt;, covering areas such as customers, vehicles, locations, sales, payments, delivery and costs.&lt;/p&gt;

&lt;p&gt;My goal was to turn the raw transactional data into a Power BI report that could help answer questions around:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Sales performance&lt;/li&gt;
&lt;li&gt;Revenue and profitability&lt;/li&gt;
&lt;li&gt;Vehicle performance&lt;/li&gt;
&lt;li&gt;Customer and sales channels&lt;/li&gt;
&lt;li&gt;Delivery performance&lt;/li&gt;
&lt;li&gt;Operational costs&lt;/li&gt;
&lt;li&gt;Returns and cancellations&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;But before answering those questions, I needed to understand what each row actually represented.&lt;/p&gt;

&lt;h3&gt;
  
  
  Understanding the grain
&lt;/h3&gt;

&lt;p&gt;I established that one row represented one transaction/order record.&lt;/p&gt;

&lt;p&gt;That sounds straightforward, but it became important later when I started investigating duplicate Order IDs.&lt;/p&gt;

&lt;h2&gt;
  
  
  The First Problem: Order IDs Were Not Always Unique
&lt;/h2&gt;

&lt;p&gt;One of the first things I noticed was that some Order IDs appeared more than once.&lt;/p&gt;

&lt;p&gt;The duplicated IDs included:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;code&gt;ord1020&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;CAR1086&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;ord1174&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;At first, deleting duplicates might seem like the obvious solution.&lt;/p&gt;

&lt;p&gt;But I decided to investigate them instead.&lt;/p&gt;

&lt;p&gt;The duplicate records contained differences in other attributes such as customer information, location and customer type.&lt;/p&gt;

&lt;p&gt;That raised an important question:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Were these actually duplicate records, or were they separate transaction records sharing the same source Order ID?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Without a separate transaction identifier, I could not confidently assume that the records were duplicates.&lt;/p&gt;

&lt;p&gt;So instead of deleting them, I retained the records and created a unique &lt;strong&gt;Transaction Key&lt;/strong&gt; for each fact-row.&lt;/p&gt;

&lt;p&gt;The original Order ID was retained as a business/source reference.&lt;/p&gt;

&lt;p&gt;This was one of the biggest lessons from the project:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;A duplicate value is not automatically a duplicate record.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Sometimes the right response to a data-quality problem is not to delete data, but to investigate what the data is actually telling you.&lt;/p&gt;

&lt;h2&gt;
  
  
  Investigating Before Cleaning
&lt;/h2&gt;

&lt;p&gt;I used Excel extensively during the investigation stage.&lt;/p&gt;

&lt;p&gt;This allowed me to look at the dataset column by column and identify patterns before transforming anything.&lt;/p&gt;

&lt;p&gt;I found inconsistencies across several fields, including:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Customer Type&lt;/li&gt;
&lt;li&gt;Region&lt;/li&gt;
&lt;li&gt;County&lt;/li&gt;
&lt;li&gt;City&lt;/li&gt;
&lt;li&gt;Branch&lt;/li&gt;
&lt;li&gt;Lead Source&lt;/li&gt;
&lt;li&gt;Car Make&lt;/li&gt;
&lt;li&gt;Fuel Type&lt;/li&gt;
&lt;li&gt;Transmission&lt;/li&gt;
&lt;li&gt;Vehicle Year&lt;/li&gt;
&lt;li&gt;Discount&lt;/li&gt;
&lt;li&gt;Prices and costs&lt;/li&gt;
&lt;li&gt;Delivery dates&lt;/li&gt;
&lt;li&gt;Delivery status&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;There were also values such as blanks, &lt;code&gt;N/A&lt;/code&gt;, &lt;code&gt;NULL&lt;/code&gt;, inconsistent capitalisation, spelling variations and values that required further investigation.&lt;/p&gt;

&lt;p&gt;For example, vehicle makes contained variations such as &lt;code&gt;Toyota&lt;/code&gt;, &lt;code&gt;TOYOTA&lt;/code&gt; and other misspelled variants.&lt;/p&gt;

&lt;p&gt;The important distinction for me was between &lt;strong&gt;standardising a genuine variation&lt;/strong&gt; and &lt;strong&gt;assuming two different values represented the same real-world entity&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;That distinction became particularly important for fields such as sales representatives, where names like &lt;code&gt;Faith&lt;/code&gt; and &lt;code&gt;Faith Achieng&lt;/code&gt; appeared.&lt;/p&gt;

&lt;p&gt;I did not automatically merge these because the dataset did not provide enough evidence to prove they represented the same person.&lt;/p&gt;

&lt;h2&gt;
  
  
  Cleaning and Transformation
&lt;/h2&gt;

&lt;p&gt;After investigating the raw data, I moved into Power Query.&lt;/p&gt;

&lt;p&gt;The goal was not simply to make the dataset look cleaner.&lt;/p&gt;

&lt;p&gt;The goal was to make the data more consistent, usable and analytically reliable while preserving the underlying transaction records.&lt;/p&gt;

&lt;p&gt;The cleaning process included:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Standardising categorical values&lt;/li&gt;
&lt;li&gt;Correcting inconsistent text formats&lt;/li&gt;
&lt;li&gt;Handling missing and placeholder values&lt;/li&gt;
&lt;li&gt;Converting columns to appropriate data types&lt;/li&gt;
&lt;li&gt;Validating numerical fields&lt;/li&gt;
&lt;li&gt;Investigating date anomalies&lt;/li&gt;
&lt;li&gt;Creating calculated fields required for analysis&lt;/li&gt;
&lt;li&gt;Checking the results after transformation&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;After the cleaning process, the final fact table retained all &lt;strong&gt;276 records&lt;/strong&gt;, with &lt;strong&gt;zero technical Power Query errors&lt;/strong&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Dates Needed Special Attention
&lt;/h2&gt;

&lt;p&gt;Dates were one of the more complicated areas of the dataset.&lt;/p&gt;

&lt;p&gt;The source contained a mixture of valid dates, blanks, placeholder values, Excel serial numbers and invalid dates.&lt;/p&gt;

&lt;p&gt;I also found delivery records where the calculated delivery period was negative.&lt;/p&gt;

&lt;p&gt;Four examples were:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;code&gt;LCL-1080&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;CAR1219&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;ord1229&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;LCL1236&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Instead of silently removing these records, I treated them as data-quality considerations that needed to remain visible during analysis.&lt;/p&gt;

&lt;p&gt;That approach matters because cleaning data does not mean pretending that problematic records never existed.&lt;/p&gt;

&lt;h2&gt;
  
  
  Building the Data Model
&lt;/h2&gt;

&lt;p&gt;Once the data was prepared, I moved from cleaning into modelling.&lt;/p&gt;

&lt;p&gt;Rather than keeping everything in one large table, I created a star-schema structure around a central &lt;strong&gt;Fact Sales&lt;/strong&gt; table.&lt;/p&gt;

&lt;p&gt;The model included dimensions for:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Customer&lt;/li&gt;
&lt;li&gt;Vehicle&lt;/li&gt;
&lt;li&gt;Location&lt;/li&gt;
&lt;li&gt;Sales&lt;/li&gt;
&lt;li&gt;Payment&lt;/li&gt;
&lt;li&gt;Delivery&lt;/li&gt;
&lt;li&gt;Date&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The fact table contained the transactional measures and keys needed for analysis.&lt;/p&gt;

&lt;p&gt;I also created a dedicated Date table covering &lt;strong&gt;1 January 2025 to 15 July 2026&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;The Order Date relationship was active, while Delivery Date was kept inactive so that delivery-related analysis could use it when specifically required.&lt;/p&gt;

&lt;p&gt;This was another point where the project moved beyond simply creating visuals.&lt;/p&gt;

&lt;p&gt;The model structure determines how reliably those visuals answer questions.&lt;/p&gt;

&lt;h2&gt;
  
  
  Adding Business Measures with DAX
&lt;/h2&gt;

&lt;p&gt;With the model in place, I created measures for the main business questions.&lt;/p&gt;

&lt;p&gt;Some of the measures included:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Total Revenue&lt;/li&gt;
&lt;li&gt;Total Units Sold&lt;/li&gt;
&lt;li&gt;Total Cost&lt;/li&gt;
&lt;li&gt;Total Profit&lt;/li&gt;
&lt;li&gt;Profit Margin&lt;/li&gt;
&lt;li&gt;Average Delivery Days&lt;/li&gt;
&lt;li&gt;Average Discount&lt;/li&gt;
&lt;li&gt;Total Delivery Fees&lt;/li&gt;
&lt;li&gt;Total Logistics Cost&lt;/li&gt;
&lt;li&gt;Revenue Per Unit&lt;/li&gt;
&lt;li&gt;Total Transactions&lt;/li&gt;
&lt;li&gt;Average Units per Transaction&lt;/li&gt;
&lt;li&gt;Profit per Transaction&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;One example was Total Cost, calculated from units sold and unit cost rather than simply summing a pre-existing total.&lt;/p&gt;

&lt;p&gt;This allowed the report to analyse profitability at a more meaningful level.&lt;/p&gt;

&lt;h2&gt;
  
  
  Turning the Model Into a Report
&lt;/h2&gt;

&lt;p&gt;The final Power BI report was organised into three pages.&lt;/p&gt;

&lt;h3&gt;
  
  
  1. Executive Overview
&lt;/h3&gt;

&lt;p&gt;This page focused on the overall performance of the business.&lt;/p&gt;

&lt;p&gt;The main KPIs included:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;$1.273B Total Revenue&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;$455.46M Total Profit&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;36% Profit Margin&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;452 Units Sold&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;276 Transactions&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;16.76 Average Delivery Days&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The purpose was to give a decision-maker a quick view of the overall situation before going deeper.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. Sales &amp;amp; Profitability
&lt;/h3&gt;

&lt;p&gt;This page explored sales volume, revenue and profitability across dimensions such as vehicle make, location and lead source.&lt;/p&gt;

&lt;p&gt;One example from the analysis was Toyota, which generated approximately &lt;strong&gt;$540.8M in revenue from 137 units&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;At the same time, not every high-revenue category performed equally from a profitability perspective.&lt;/p&gt;

&lt;p&gt;For example, BMW generated approximately &lt;strong&gt;$50.9M in revenue but had a negative margin of around 2%&lt;/strong&gt; in this dataset.&lt;/p&gt;

&lt;p&gt;That creates a business question rather than simply being a number on a chart:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What is driving the negative profitability?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Potential areas for further investigation would include acquisition cost, selling price, discounts and the underlying transaction records.&lt;/p&gt;

&lt;h3&gt;
  
  
  3. Delivery &amp;amp; Operations
&lt;/h3&gt;

&lt;p&gt;The third page focused on delivery performance and operational costs.&lt;/p&gt;

&lt;p&gt;The dataset contained several delivery statuses, including:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Delivered&lt;/li&gt;
&lt;li&gt;In Transit&lt;/li&gt;
&lt;li&gt;Cancelled&lt;/li&gt;
&lt;li&gt;At Yard&lt;/li&gt;
&lt;li&gt;Delayed&lt;/li&gt;
&lt;li&gt;Held&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The report allowed delivery performance and delivery-related costs to be examined alongside those statuses.&lt;/p&gt;

&lt;p&gt;For example, records marked as Held had an average delivery period of approximately &lt;strong&gt;25 days&lt;/strong&gt;, compared with approximately &lt;strong&gt;15.4 days for Delivered records&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Again, the dashboard does not automatically explain &lt;em&gt;why&lt;/em&gt; this happens.&lt;/p&gt;

&lt;p&gt;It provides a starting point for asking the next business question.&lt;/p&gt;

&lt;h2&gt;
  
  
  What I Learned
&lt;/h2&gt;

&lt;p&gt;The biggest lesson from this project was that &lt;strong&gt;Power BI is not just about building dashboards&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;The dashboard is the visible part.&lt;/p&gt;

&lt;p&gt;A large part of the actual analytical work happens before the first visual is created.&lt;/p&gt;

&lt;p&gt;I learned to ask questions such as:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What does one row represent?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Can I trust this identifier to be unique?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Is this really a duplicate, or just a repeated value?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Should these two values actually be merged?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What does this missing value mean?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Is this date valid?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Does this relationship make business sense?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What assumptions am I making?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Those questions changed the way I approached the project.&lt;/p&gt;

&lt;h2&gt;
  
  
  Final Takeaway
&lt;/h2&gt;

&lt;p&gt;I went from looking at a 46-column dataset and asking &lt;em&gt;“What visual should I build?”&lt;/em&gt; to asking:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;“What does this data actually allow me to conclude?”&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;That shift has probably been the most valuable part of the project.&lt;/p&gt;

&lt;p&gt;Power BI helped me turn the data into something visual and interactive.&lt;/p&gt;

&lt;p&gt;But the real work was learning how to question the data before trusting the numbers.&lt;/p&gt;

&lt;p&gt;And that is the part of data analytics I want to keep developing.&lt;/p&gt;

&lt;p&gt;Github: [(&lt;a href="https://github.com/majalealex-ux/JCars-Sales-Performance-Analysis/tree/main)" rel="noopener noreferrer"&gt;https://github.com/majalealex-ux/JCars-Sales-Performance-Analysis/tree/main)&lt;/a&gt;]&lt;/p&gt;

</description>
      <category>analytics</category>
      <category>data</category>
      <category>software</category>
    </item>
    <item>
      <title>Understanding Data Modelling, Relationships and Joins in Power BI</title>
      <dc:creator>Alex Majale</dc:creator>
      <pubDate>Mon, 14 Sep 2026 12:07:59 +0000</pubDate>
      <link>https://dev.to/alex_majale_d64efa6d81883/understanding-data-modelling-relationships-and-joins-in-power-bi-1ebe</link>
      <guid>https://dev.to/alex_majale_d64efa6d81883/understanding-data-modelling-relationships-and-joins-in-power-bi-1ebe</guid>
      <description>&lt;h2&gt;
  
  
  Introduction
&lt;/h2&gt;

&lt;p&gt;Data does not always come in a structure that is ready for analysis.&lt;br&gt;
For example, sales information may be stored separately from customer details, product information, dates and locations.Before creating reports, visuals or doing calculations in Power BI, it is important to understand how these different tables should be organised and connected.&lt;/p&gt;
&lt;h2&gt;
  
  
  Data Modeling
&lt;/h2&gt;

&lt;p&gt;Data modelling is the process of organising data into a structure that makes it easier to analyse and report on. &lt;br&gt;
A well-designed data model helps Power BI understand how information from different tables is connected, allowing filters, calculations and reports to work correctly. The way a model is designed can also affect the performance, scalability and maintainability of a Power BI report.&lt;/p&gt;
&lt;h3&gt;
  
  
  Flat Table
&lt;/h3&gt;

&lt;p&gt;A flat table is a simple way of organising data where most or all the information is stored in one table.&lt;br&gt;
This is common in spreadsheet tools such as Microsoft Excel, where a dataset may contain multiple types of information in the same table.&lt;br&gt;
For example, in an e-commerce business, one table could contain information such as the Order ID, date, customer name, customer email, address, product name, product category, unit price, quantity and total sales amount. Each row may represent a transaction or an individual product within a transaction.&lt;br&gt;
While this structure can be easy to understand and work with, problems can start to appear as the data grows.&lt;br&gt;
Customer and product information may be repeated across many rows. For example, a customer who has placed several orders may have their name, email and address repeated for every transaction.&lt;br&gt;
This repetition increases the size of the dataset and can make it more difficult to maintain.&lt;br&gt;
If customer or product information needs to be updated, the same change may need to be made in multiple records.&lt;br&gt;
As the dataset becomes larger, a flat table can also become less efficient and more difficult to manage compared to a properly structured data model.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Order Id&lt;/th&gt;
&lt;th&gt;Date&lt;/th&gt;
&lt;th&gt;Customer name&lt;/th&gt;
&lt;th&gt;Customer email&lt;/th&gt;
&lt;th&gt;Address&lt;/th&gt;
&lt;th&gt;Product name&lt;/th&gt;
&lt;th&gt;Product Category&lt;/th&gt;
&lt;th&gt;Unit Price&lt;/th&gt;
&lt;th&gt;Quantity&lt;/th&gt;
&lt;th&gt;Total Sales Amount&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;S001&lt;/td&gt;
&lt;td&gt;25/08/2005&lt;/td&gt;
&lt;td&gt;Joseph Kamau&lt;/td&gt;
&lt;td&gt;&lt;a href="mailto:josephk@email.com"&gt;josephk@email.com&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;Nairobi&lt;/td&gt;
&lt;td&gt;Mouse&lt;/td&gt;
&lt;td&gt;Electronics&lt;/td&gt;
&lt;td&gt;Kes 250&lt;/td&gt;
&lt;td&gt;10&lt;/td&gt;
&lt;td&gt;Kes 2500&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;S002&lt;/td&gt;
&lt;td&gt;26/08/2005&lt;/td&gt;
&lt;td&gt;Mitchell Faraja&lt;/td&gt;
&lt;td&gt;&lt;a href="mailto:mitchellf@email.com"&gt;mitchellf@email.com&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;Mombasa&lt;/td&gt;
&lt;td&gt;Laptop&lt;/td&gt;
&lt;td&gt;Electronics&lt;/td&gt;
&lt;td&gt;Kes 20000&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;Kes 40000&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;S003&lt;/td&gt;
&lt;td&gt;27/08/2005&lt;/td&gt;
&lt;td&gt;Joseph Kamau&lt;/td&gt;
&lt;td&gt;&lt;a href="mailto:josephk@email.com"&gt;josephk@email.com&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;Kisumu&lt;/td&gt;
&lt;td&gt;Keyboard&lt;/td&gt;
&lt;td&gt;Electronics&lt;/td&gt;
&lt;td&gt;Kes 500&lt;/td&gt;
&lt;td&gt;5&lt;/td&gt;
&lt;td&gt;2500&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;
&lt;h4&gt;
  
  
  Advantages
&lt;/h4&gt;

&lt;ul&gt;
&lt;li&gt;Easy to understand initially&lt;/li&gt;
&lt;li&gt;Simple for very small datasets&lt;/li&gt;
&lt;li&gt;No relationships required&lt;/li&gt;
&lt;li&gt;Quick to create&lt;/li&gt;
&lt;/ul&gt;
&lt;h4&gt;
  
  
  Disadvantages
&lt;/h4&gt;

&lt;ul&gt;
&lt;li&gt;Repeated data&lt;/li&gt;
&lt;li&gt;Larger storage requirements&lt;/li&gt;
&lt;li&gt;Difficult to maintain&lt;/li&gt;
&lt;li&gt;Less scalable&lt;/li&gt;
&lt;li&gt;Can create unnecessary redundancy&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;
  
  
  The Limitations of a Flat Table
&lt;/h3&gt;

&lt;p&gt;Consider an e-commerce dataset where sales transactions, customer information and product details are all stored in one large worksheet.&lt;br&gt;
If a customer named Joseph Kamau buys ten different products, his name, email address and address may be repeated across all ten records.&lt;/p&gt;

&lt;p&gt;This creates duplication of information. It can also create problems when information needs to be updated. For example, if Joseph changes his address and only some of his records are updated, the dataset wil contain different addresses for the same customer. This can affect the accuracy and consistency of the data.&lt;/p&gt;

&lt;p&gt;As the dataset grows, storing everything in one wide table can also make analysis more difficult and less efficient. The same customer and product information may be repeated thousands or even millions of times. Calculations such as counting unique customers or analysing average sales per customer may therefore require Power BI to process a large amount of repeated information.&lt;/p&gt;

&lt;p&gt;One way to solve this is by separating the data into different tables based on the type of information being stored. Instead of storing Joseph's details in every sales record, his information can be stored once in a Customers table. The Sales table would then contain a CustomerID that connects each sale to the correct customer.&lt;/p&gt;

&lt;p&gt;The same approach can be used for products. Product information can be stored in a separate Products table, while the Sales table contains the ProductID, quantity purchased and transaction details. This reduces unnecessary repetition and creates a more organised structure where customer and product information can be managed separately from sales transactions.&lt;/p&gt;
&lt;h3&gt;
  
  
  Customers Table
&lt;/h3&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;CustomerID&lt;/th&gt;
&lt;th&gt;CustomerName&lt;/th&gt;
&lt;th&gt;Email&lt;/th&gt;
&lt;th&gt;Address&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;C001&lt;/td&gt;
&lt;td&gt;Joseph Kamau&lt;/td&gt;
&lt;td&gt;&lt;a href="mailto:josephk@email.com"&gt;josephk@email.com&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;Nairobi&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;C002&lt;/td&gt;
&lt;td&gt;Mitchell Faraja&lt;/td&gt;
&lt;td&gt;&lt;a href="mailto:mitchellf@email.com"&gt;mitchellf@email.com&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;Mombasa&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;
&lt;h3&gt;
  
  
  Products Table
&lt;/h3&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;ProductID&lt;/th&gt;
&lt;th&gt;ProductName&lt;/th&gt;
&lt;th&gt;Category&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;P101&lt;/td&gt;
&lt;td&gt;Laptop&lt;/td&gt;
&lt;td&gt;Electronics&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;P102&lt;/td&gt;
&lt;td&gt;Mouse&lt;/td&gt;
&lt;td&gt;Electronics&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;P103&lt;/td&gt;
&lt;td&gt;Keyboard&lt;/td&gt;
&lt;td&gt;Electronics&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;
&lt;h3&gt;
  
  
  Sales Table
&lt;/h3&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;OrderID&lt;/th&gt;
&lt;th&gt;Date&lt;/th&gt;
&lt;th&gt;CustomerID&lt;/th&gt;
&lt;th&gt;ProductID&lt;/th&gt;
&lt;th&gt;Quantity&lt;/th&gt;
&lt;th&gt;Unit Price&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;S001&lt;/td&gt;
&lt;td&gt;25/08/2005&lt;/td&gt;
&lt;td&gt;C001&lt;/td&gt;
&lt;td&gt;P102&lt;/td&gt;
&lt;td&gt;10&lt;/td&gt;
&lt;td&gt;250&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;S002&lt;/td&gt;
&lt;td&gt;26/08/2005&lt;/td&gt;
&lt;td&gt;C002&lt;/td&gt;
&lt;td&gt;P101&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;20000&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;S003&lt;/td&gt;
&lt;td&gt;27/08/2005&lt;/td&gt;
&lt;td&gt;C001&lt;/td&gt;
&lt;td&gt;P103&lt;/td&gt;
&lt;td&gt;5&lt;/td&gt;
&lt;td&gt;500&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Once the data has been separated into different tables, the next step is to understand the role of each table. Not every table in a data model serves the same purpose. In the example, the Customers table contains information about customers, the Products table contains information about products, while the Sales table records the actual transactions that take place.&lt;/p&gt;

&lt;p&gt;This leads to two important types of tables used in analytical data models: &lt;strong&gt;fact tables and dimension tables&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;A &lt;strong&gt;fact table&lt;/strong&gt; records business events or transactions that the organisation wants to analyse. In the example, the Sales table can be treated as the fact table because each row represents a sale. It contains information such as the transaction date, CustomerID, ProductID, quantity and unit price.&lt;/p&gt;

&lt;p&gt;For example:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;OrderID&lt;/th&gt;
&lt;th&gt;Date&lt;/th&gt;
&lt;th&gt;CustomerID&lt;/th&gt;
&lt;th&gt;ProductID&lt;/th&gt;
&lt;th&gt;Quantity&lt;/th&gt;
&lt;th&gt;Unit Price&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;S001&lt;/td&gt;
&lt;td&gt;25/08/2005&lt;/td&gt;
&lt;td&gt;C001&lt;/td&gt;
&lt;td&gt;P102&lt;/td&gt;
&lt;td&gt;10&lt;/td&gt;
&lt;td&gt;250&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;S002&lt;/td&gt;
&lt;td&gt;26/08/2005&lt;/td&gt;
&lt;td&gt;C002&lt;/td&gt;
&lt;td&gt;P101&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;20000&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;S003&lt;/td&gt;
&lt;td&gt;27/08/2005&lt;/td&gt;
&lt;td&gt;C001&lt;/td&gt;
&lt;td&gt;P103&lt;/td&gt;
&lt;td&gt;5&lt;/td&gt;
&lt;td&gt;500&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The &lt;strong&gt;Customers&lt;/strong&gt; and &lt;strong&gt;Products&lt;/strong&gt; tables, on the other hand, provide descriptive information about the entities involved in those sales. These are known as &lt;strong&gt;dimension tables&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;The Customers table contain:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;CustomerID&lt;/th&gt;
&lt;th&gt;CustomerName&lt;/th&gt;
&lt;th&gt;Email&lt;/th&gt;
&lt;th&gt;Address&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;C001&lt;/td&gt;
&lt;td&gt;Joseph Kamau&lt;/td&gt;
&lt;td&gt;&lt;a href="mailto:josephk@email.com"&gt;josephk@email.com&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;Nairobi&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;C002&lt;/td&gt;
&lt;td&gt;Mitchell Faraja&lt;/td&gt;
&lt;td&gt;&lt;a href="mailto:mitchellf@email.com"&gt;mitchellf@email.com&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;Mombasa&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The Products table could contain:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;ProductID&lt;/th&gt;
&lt;th&gt;ProductName&lt;/th&gt;
&lt;th&gt;Category&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;P101&lt;/td&gt;
&lt;td&gt;Laptop&lt;/td&gt;
&lt;td&gt;Electronics&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;P102&lt;/td&gt;
&lt;td&gt;Mouse&lt;/td&gt;
&lt;td&gt;Electronics&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;P103&lt;/td&gt;
&lt;td&gt;Keyboard&lt;/td&gt;
&lt;td&gt;Electronics&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The main difference is that the fact table tells us &lt;strong&gt;what happened&lt;/strong&gt;, while the dimension tables provide the context needed to understand and analyse what happened. For example, FactSales can tell us that a sale of KES 2500 occurred, while DimCustomer tells us who made the purchase and DimProduct tells us what was purchased.&lt;/p&gt;

&lt;p&gt;This separation also helps reduce unnecessary duplication. Instead of storing Joseph's name, email and location in every sales record, the Sales table only needs to store her CustomerID. The same applies to products, where the Sales table stores ProductID instead of repeating the product name and category.&lt;/p&gt;

&lt;p&gt;The tables can then be connected using keys. CustomerID is the primary key in DimCustomer and acts as a foreign key in FactSales. Similarly, ProductID is the primary key in DimProduct and a foreign key in FactSales.&lt;/p&gt;
&lt;h3&gt;
  
  
  Grain
&lt;/h3&gt;

&lt;p&gt;Grain describes the level of detail represented by each row in a fact table. In simple terms, it answers the question: &lt;strong&gt;what exactly does one row represent?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;For example, in an e-commerce &lt;code&gt;FactSales&lt;/code&gt; table, the grain could be defined as &lt;strong&gt;one row representing one product sold as part of a sales transaction&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Using this grain, a single order containing three different products would result in three rows in the fact table:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Order O001

Laptop      → 20000
Mouse       → 250
Keyboard    → 500
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Each row represents a specific product within that transaction, allowing measures such as quantity sold and sales amount to be analysed at the product, customer or transaction level.&lt;/p&gt;

&lt;p&gt;Defining the grain before building a fact table is important because it ensures that the data is stored at a consistent level of detail. It also helps prevent incorrect calculations, such as counting transactions multiple times or combining data that is recorded at different levels.&lt;/p&gt;

&lt;p&gt;The grain does not have to be the same for every fact table. For example, one fact table could record individual sales transactions, while another could record daily inventory levels. What matters is that the grain of each fact table is clearly defined and consistently maintained.&lt;/p&gt;

&lt;h3&gt;
  
  
  Primary Keys and Foreign Keys
&lt;/h3&gt;

&lt;p&gt;After separating the data into different tables, the next question is how those tables will be connected. This is where primary keys and foreign keys become important.&lt;/p&gt;

&lt;p&gt;A &lt;strong&gt;primary key&lt;/strong&gt; is a column that uniquely identifies each record in a table. For example, in a DimCustomer table, CustomerID can be used as the primary key because each customer should have their own unique CustomerID.&lt;/p&gt;

&lt;p&gt;For example:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;CustomerID&lt;/th&gt;
&lt;th&gt;CustomerName&lt;/th&gt;
&lt;th&gt;Email&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;C001&lt;/td&gt;
&lt;td&gt;Joseph Kamau&lt;/td&gt;
&lt;td&gt;&lt;a href="mailto:josephk@email.com"&gt;josephk@email.com&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;C002&lt;/td&gt;
&lt;td&gt;Mitchell Faraja&lt;/td&gt;
&lt;td&gt;&lt;a href="mailto:mitchellf@email.com"&gt;mitchellf@email.com&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;In the example, CustomerID is unique for every customer. The same CustomerID should not identify two different customers in the dimension table.&lt;/p&gt;

&lt;p&gt;A &lt;strong&gt;foreign key&lt;/strong&gt; is a column in another table that refers to the primary key. In the FactSales table, CustomerID acts as a foreign key because it connects each sale to the customer who made the purchase.&lt;/p&gt;

&lt;p&gt;For example:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;SalesID&lt;/th&gt;
&lt;th&gt;CustomerID&lt;/th&gt;
&lt;th&gt;ProductID&lt;/th&gt;
&lt;th&gt;SalesAmount&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;S001&lt;/td&gt;
&lt;td&gt;C001&lt;/td&gt;
&lt;td&gt;P102&lt;/td&gt;
&lt;td&gt;250&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;S002&lt;/td&gt;
&lt;td&gt;C002&lt;/td&gt;
&lt;td&gt;P101&lt;/td&gt;
&lt;td&gt;20000&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;S003&lt;/td&gt;
&lt;td&gt;C001&lt;/td&gt;
&lt;td&gt;P103&lt;/td&gt;
&lt;td&gt;500&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Here, C001 appears several times because Joseph has made multiple purchases. This is different from the DimCustomer table, where C001 appears only once.&lt;/p&gt;

&lt;p&gt;This creates an important pattern in Power BI: the primary key is normally found on the &lt;strong&gt;one side&lt;/strong&gt; of a relationship, while the foreign key appears on the &lt;strong&gt;many side&lt;/strong&gt;. In this example, one customer can have many sales.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;DimCustomer                    FactSales

CustomerID                     CustomerID
   C001  ─────────────────────── C001
   C002  ─────────────────────── C001
   C003                         C002
                                C001
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The keys therefore provide the link between the descriptive information in the dimension table and the transactions stored in the fact table.&lt;/p&gt;

&lt;h3&gt;
  
  
  Cardinality
&lt;/h3&gt;

&lt;p&gt;After identifying the keys that connect tables, the next thing to understand is &lt;strong&gt;cardinality&lt;/strong&gt;. Cardinality describes how many records in one table can be related to records in another table.&lt;/p&gt;

&lt;p&gt;There are three main types of cardinality that are commonly used in Power BI: &lt;strong&gt;one-to-many, one-to-one and many-to-many&lt;/strong&gt;.&lt;/p&gt;

&lt;h4&gt;
  
  
  One-to-Many (1:*)
&lt;/h4&gt;

&lt;p&gt;One-to-many is the most common relationship in a Power BI data model. It means that one record in one table can be related to many records in another table.&lt;/p&gt;

&lt;p&gt;For example, one customer can make multiple purchases. In this case, DimCustomer is on the one side and FactSales is on the many side.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;DimCustomer                     FactSales

CustomerKey                     CustomerKey
C001  ────────────────────────▶  C001
C002  ────────────────────────▶  C001
C003  ────────────────────────▶  C002
                                C001
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The CustomerKey appears only once for each customer in DimCustomer, but the same CustomerKey can appear many times in FactSales as the customer makes different purchases.&lt;/p&gt;

&lt;p&gt;Filters can also flow from the dimension table to the fact table. For example, if a report user selects &lt;strong&gt;Nairobi&lt;/strong&gt; from DimCustomer[Address], Power BI can use the relationship to filter the relevant records in FactSales.&lt;/p&gt;

&lt;h4&gt;
  
  
  One-to-One (1:1)
&lt;/h4&gt;

&lt;p&gt;A one-to-one relationship means that each record in one table is related to only one record in another table.&lt;/p&gt;

&lt;p&gt;For example, an organisation might have an Employee table and a separate EmployeeConfidentialInformation table. Each employee would have one corresponding record in each table.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Employee                    EmployeeConfidential

EmployeeID                  EmployeeID
E001  ───────────────────▶  E001
E002  ───────────────────▶  E002
E003  ───────────────────▶  E003
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  Many-to-Many (&lt;em&gt;:&lt;/em&gt;)
&lt;/h4&gt;

&lt;p&gt;A many-to-many relationship occurs when multiple records in one table can be related to multiple records in another table.&lt;/p&gt;

&lt;p&gt;For example, a student can take several courses, while each course can have many students.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Students                     Courses

Student A ────────────────▶  Course 1
Student A ────────────────▶  Course 2
Student B ────────────────▶  Course 1
Student B ────────────────▶  Course 3
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Active and Inactive Relationships
&lt;/h3&gt;

&lt;p&gt;Power BI relationships can be either &lt;strong&gt;active&lt;/strong&gt; or &lt;strong&gt;inactive&lt;/strong&gt;. An active relationship is the relationship Power BI uses by default when filters and calculations are applied between two related tables. An inactive relationship exists in the model but is not used automatically.&lt;/p&gt;

&lt;p&gt;A common example occurs when a fact table contains more than one date that needs to be analysed. For example, a &lt;code&gt;FactSales&lt;/code&gt; table may contain both &lt;code&gt;OrderDate&lt;/code&gt; and &lt;code&gt;ShipDate&lt;/code&gt;, while the model has a &lt;code&gt;DimDate&lt;/code&gt; table.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;                 ┌──────────────┐
                 │   DimDate    │
                 └──────┬───────┘
                        │
                 Active │
                        ▼
                  OrderDate
                 ┌──────────────┐
                 │  FactSales   │
                 └──────────────┘
                        ▲
              Inactive │
                        │
                   ShipDate
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;OrderDate&lt;/code&gt; relationship could be active, meaning it is used automatically when a date filter is applied. The relationship between &lt;code&gt;DimDate&lt;/code&gt; and &lt;code&gt;ShipDate&lt;/code&gt; can remain inactive because both relationships cannot normally be active between the same two tables at the same time.&lt;/p&gt;

&lt;p&gt;This does not mean the inactive relationship is useless. It can be used when a particular analysis requires it. For example, a report may normally analyse sales based on the date an order was placed, but a separate measure may need to analyse sales based on the date the order was shipped.&lt;/p&gt;

&lt;p&gt;DAX provides the &lt;code&gt;USERELATIONSHIP()&lt;/code&gt; function to temporarily use an inactive relationship within a calculation. This allows the same &lt;code&gt;DimDate&lt;/code&gt; table to support different date-based analyses without creating unnecessary duplicate tables.&lt;/p&gt;

&lt;p&gt;For example, the model could use the active &lt;code&gt;OrderDate&lt;/code&gt; relationship for normal sales analysis, while a separate measure uses the inactive &lt;code&gt;ShipDate&lt;/code&gt; relationship when analysing shipped orders.&lt;/p&gt;

&lt;p&gt;Active and inactive relationships are therefore useful when a fact table contains multiple fields that could relate to the same dimension.&lt;/p&gt;

&lt;h3&gt;
  
  
  The Star Schema
&lt;/h3&gt;

&lt;p&gt;A star schema is a data model where a central fact table is connected directly to several dimension tables. The fact table sits at the centre, while the dimension tables surround it, creating a structure that looks like a star.&lt;/p&gt;

&lt;p&gt;For example, in an e-commerce model, the &lt;code&gt;FactSales&lt;/code&gt; table could contain information about sales transactions such as SalesAmount, Quantity, CustomerKey, ProductKey and DateKey. It can then be connected directly to dimension tables such as &lt;code&gt;DimCustomer&lt;/code&gt;, &lt;code&gt;DimProduct&lt;/code&gt; and &lt;code&gt;DimDate&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;                    ┌─────────────────┐
                    │   DimCustomer   │
                    └────────┬────────┘
                             │ 1
                             │
                             │ *
┌─────────────────┐     ┌────▼────────────┐     ┌─────────────────┐
│     DimDate     │ 1 * │    FactSales    │ * 1 │   DimProduct    │
└─────────────────┘─────►│                 │◄────└─────────────────┘
                         └─────────────────┘
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The relationships are normally &lt;strong&gt;one-to-many (1:*)&lt;/strong&gt;, with the dimension table on the one side and the fact table on the many side. For example, one customer can have many sales transactions, so &lt;code&gt;DimCustomer&lt;/code&gt; is on the one side while &lt;code&gt;FactSales&lt;/code&gt; is on the many side.&lt;/p&gt;

&lt;h3&gt;
  
  
  Importance of a Star Schema
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Simpler analysis:&lt;/strong&gt;&lt;br&gt;
Dimensions provide the descriptive information used to filter and group data, while the fact table contains the numbers and events being analysed. For example, a user can select a product category from &lt;code&gt;DimProduct&lt;/code&gt; and analyse the corresponding sales from &lt;code&gt;FactSales&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Better report performance:&lt;/strong&gt;&lt;br&gt;
A well-structured star schema reduces unnecessary relationships and keeps the model relatively simple. This can help Power BI process queries and DAX calculations more efficiently, especially as the amount of data increases.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Easier to use:&lt;/strong&gt;&lt;br&gt;
Report developers can easily identify where different types of information belong. Customer attributes are in &lt;code&gt;DimCustomer&lt;/code&gt;, product attributes are in &lt;code&gt;DimProduct&lt;/code&gt;, dates are in &lt;code&gt;DimDate&lt;/code&gt;, and transaction measures are in &lt;code&gt;FactSales&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Easier to maintain:&lt;/strong&gt;&lt;br&gt;
Because each table has a clear purpose, changes to the model are easier to manage. New descriptive attributes can be added to the appropriate dimension without unnecessarily changing the structure of the fact table.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Works well with DAX and filtering:&lt;/strong&gt;&lt;br&gt;
The clear separation between dimensions and facts makes it easier to create measures and understand how filters affect the results. For example, selecting a specific product category can filter the related sales records through the relationship between &lt;code&gt;DimProduct&lt;/code&gt; and &lt;code&gt;FactSales&lt;/code&gt;.&lt;/p&gt;
&lt;h3&gt;
  
  
  The Snowflake Schema
&lt;/h3&gt;

&lt;p&gt;A snowflake schema is a variation of the star schema where dimension tables are further divided into smaller related tables. This process is known as &lt;strong&gt;normalization&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;For example, instead of keeping the product category and sub-category information directly inside &lt;code&gt;DimProduct&lt;/code&gt;, they can be separated into their own tables. &lt;code&gt;DimProduct&lt;/code&gt; would connect to &lt;code&gt;DimSubCategory&lt;/code&gt;, which would then connect to &lt;code&gt;DimCategory&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;┌─────────────────┐
│   DimCategory   │
└────────┬────────┘
         │ 1
         │
         │ *
┌────────▼────────┐
│ DimSubCategory  │
└────────┬────────┘
         │ 1
         │
         │ *
┌────────▼────────┐
│   DimProduct    │
└────────┬────────┘
         │ 1
         │
         │ *
┌────────▼────────┐
│    FactSales    │
└─────────────────┘
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Using the e-commerce example, &lt;code&gt;DimProduct&lt;/code&gt; might contain ProductID, ProductName and SubCategoryID. &lt;code&gt;DimSubCategory&lt;/code&gt; would contain SubCategoryID, SubCategoryName and CategoryID, while &lt;code&gt;DimCategory&lt;/code&gt; would contain CategoryID and CategoryName.&lt;/p&gt;

&lt;p&gt;This structure reduces repeated descriptive information. For example, instead of storing the same category information across many product records, the category can be stored once in &lt;code&gt;DimCategory&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;However, this also means that the model contains more tables and relationships. If a report needs to analyse sales by category, the filter may need to move through several tables:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;DimCategory → DimSubCategory → DimProduct → FactSales&lt;/strong&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Advantages of a Snowflake Schema
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Reduces redundancy:&lt;/strong&gt; Related information can be stored once instead of being repeated across a dimension.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;More structured dimensions:&lt;/strong&gt; Large dimensions can be divided into smaller logical tables.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Can be useful for complex data:&lt;/strong&gt; Some organisations may already have highly normalized source systems that naturally produce this structure.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Limitations of a Snowflake Schema
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;More relationships:&lt;/strong&gt; Splitting dimensions creates additional relationships that Power BI has to manage.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;More complex reporting:&lt;/strong&gt; Users may need to work across several tables to access related attributes.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;More complicated filtering:&lt;/strong&gt; Filters may have to pass through multiple tables before reaching the fact table.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;More difficult to maintain:&lt;/strong&gt; As the number of tables and relationships increases, understanding and troubleshooting the model can become harder.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Cross-Filter Direction
&lt;/h3&gt;

&lt;p&gt;After establishing a relationship between tables, the next thing to consider is &lt;strong&gt;how filters should move between those tables&lt;/strong&gt;. In Power BI, this is controlled by the &lt;strong&gt;cross-filter direction&lt;/strong&gt; of a relationship.&lt;/p&gt;

&lt;p&gt;There are two main options: &lt;strong&gt;Single direction&lt;/strong&gt; and &lt;strong&gt;Both directions (bidirectional)&lt;/strong&gt;.&lt;/p&gt;

&lt;h4&gt;
  
  
  Single Direction
&lt;/h4&gt;

&lt;p&gt;Single-direction filtering is the default and is the most common approach in a star schema. The filter normally flows from the &lt;strong&gt;one side&lt;/strong&gt; of the relationship to the &lt;strong&gt;many side&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;For example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;DimCustomer  ───────────────►  FactSales
     1                              *
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If a user selects &lt;strong&gt;Nairobi&lt;/strong&gt; from &lt;code&gt;DimCustomer&lt;/code&gt;, Power BI can use that filter to show only the sales associated with customers from Nairobi in &lt;code&gt;FactSales&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;However, the filter does not automatically travel in the opposite direction. Filtering &lt;code&gt;FactSales&lt;/code&gt; does not change the available values in &lt;code&gt;DimCustomer&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;This direction is usually preferred because it makes the model easier to understand and keeps filter behaviour predictable.&lt;/p&gt;

&lt;h4&gt;
  
  
  Both Directions
&lt;/h4&gt;

&lt;p&gt;With &lt;strong&gt;Both&lt;/strong&gt; selected, filters can move in both directions between the related tables.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;DimCustomer  ◄──────────────►  FactSales
     1                              *
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For example, filtering &lt;code&gt;FactSales&lt;/code&gt; could also affect the values available in &lt;code&gt;DimCustomer&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Power Query Joins
&lt;/h2&gt;

&lt;p&gt;Power Query provides tools for preparing and transforming data before it is loaded into the Power BI data model. One of these tools is &lt;strong&gt;Merge Queries&lt;/strong&gt;, which allows data from two tables to be combined based on a common column.&lt;/p&gt;

&lt;p&gt;For example, suppose an organisation has a &lt;code&gt;Sales&lt;/code&gt; table containing &lt;code&gt;CustomerID&lt;/code&gt;, but the customer's name and location are stored in a separate &lt;code&gt;Customers&lt;/code&gt; table.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Customers
┌────────────┬──────────────┬───────────┐
│ CustomerID │ CustomerName │ City      │
├────────────┼──────────────┼───────────┤
│ C001 │ Joseph Kamau │ Nairobi│
│ C002│ Mitchell Faraja │ Mombasa│ 
│ C003 │ Joseph Kamau      │ Kisumu    │
└────────────┴──────────────┴───────────┘

Sales
┌─────────┬────────────┬─────────────┐
│ SalesID │ CustomerID │ SalesAmount │
├─────────┼────────────┼─────────────┤
│ S001    │ C001       │ 2500         │
│ S002    │ C002       │ 40000         │
│ S003    │ C001       │ 2500         │
└─────────┴────────────┴─────────────┘
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The two tables have a common &lt;code&gt;CustomerID&lt;/code&gt; column. In Power Query, this column can be used as the &lt;strong&gt;matching column&lt;/strong&gt; when merging the tables.&lt;/p&gt;

&lt;p&gt;After the merge, information from the &lt;code&gt;Customers&lt;/code&gt; table can be brought into the &lt;code&gt;Sales&lt;/code&gt; table:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Sales + Customers
        ↓
      Merge
        ↓
┌─────────┬────────────┬──────────────┬───────────┬─────────────┐
│ SalesID │ CustomerID │ CustomerName │ City      │ SalesAmount │
├─────────┼────────────┼──────────────┼───────────┼─────────────┤
│ S001    │ C001       │ Joseph Kamau       │ Nairobi   │ 2500        │
│ S002    │ C002       │ Mitchell Faraja        │ Mombasa   │ 40000         │
│ S003    │ C001       │ Joseph Kamau       │ Kisumu   │ 2500          │
└─────────┴────────────┴──────────────┴───────────┴─────────────┘
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The important point is that &lt;strong&gt;Merge Queries physically brings data from one table into another during the Power Query transformation stage&lt;/strong&gt;. It is therefore different from creating a relationship in the Power BI model.&lt;/p&gt;

&lt;p&gt;When performing a merge, Power Query requires a column or columns that can be used to match records between the two tables. In this example, &lt;code&gt;CustomerID&lt;/code&gt; is the matching column.&lt;/p&gt;

&lt;p&gt;Power Query provides several types of joins that determine &lt;strong&gt;which records from the two tables are retained in the merged result&lt;/strong&gt;. These include Left Outer, Right Outer, Full Outer, Inner, Left Anti and Right Anti joins.&lt;/p&gt;

&lt;p&gt;Understanding what each join keeps is important because choosing the wrong join can result in missing records or unexpected data in the final table.&lt;/p&gt;

&lt;h3&gt;
  
  
  Left Outer Join
&lt;/h3&gt;

&lt;p&gt;A &lt;strong&gt;Left Outer Join&lt;/strong&gt; keeps all the records from the first, or left, table and brings in matching records from the second, or right, table. If a record in the left table does not have a match in the right table, the record is still retained, but the columns brought from the right table will contain blank or null values.&lt;/p&gt;

&lt;p&gt;For example, suppose the &lt;code&gt;Sales&lt;/code&gt; table is the left table and &lt;code&gt;Customers&lt;/code&gt; is the right table.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Sales                         Customers
┌─────────┬────────────┐      ┌────────────┬──────────────┐
│ SalesID │ CustomerID │      │ CustomerID │ CustomerName │
├─────────┼────────────┤      ├────────────┼──────────────┤
│ S001    │ C001       │      │ C001       │ Joseph Kamau       │
│ S002    │ C002       │      │ C002       │ Mitchell Faraja       │
│ S003    │ C003       │      │ C003       │ Joseph Kamau      │
│ S004    │ C004       │      └────────────┴──────────────┘
└─────────┴────────────┘
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If we merge these tables using &lt;code&gt;CustomerID&lt;/code&gt; and select &lt;strong&gt;Left Outer&lt;/strong&gt;, every record from &lt;code&gt;Sales&lt;/code&gt; is retained.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Result

┌─────────┬────────────┬──────────────┐
│ SalesID │ CustomerID │ CustomerName │
├─────────┼────────────┼──────────────┤
│ S001    │ C001       │ Joseph Kamau       │
│ S002    │ C002       │ Mitchell Faraja        │
│ S003    │ C003       │ Joseph Kamau       │
│ S004    │ C004       │ null         │
└─────────┴────────────┴──────────────┘
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;S004&lt;/code&gt; is retained even though &lt;code&gt;C004&lt;/code&gt; does not exist in the &lt;code&gt;Customers&lt;/code&gt; table. Because there is no matching customer record, &lt;code&gt;CustomerName&lt;/code&gt; is returned as null.&lt;/p&gt;

&lt;p&gt;This makes a Left Outer Join useful when the &lt;strong&gt;left table is the main dataset that must be preserved&lt;/strong&gt;, while information from another table is being added where a match exists.&lt;/p&gt;

&lt;p&gt;For example, an analyst could use a Left Outer Join to keep every sales transaction while adding customer names, locations or other customer attributes from a separate table.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;In simple terms:&lt;/strong&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Left Outer = keep everything from the left table + matching records from the right table.&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  Right Outer Join
&lt;/h3&gt;

&lt;p&gt;A &lt;strong&gt;Right Outer Join&lt;/strong&gt; keeps all records from the second, or right, table and brings in matching records from the first, or left, table. If a record exists in the right table but has no matching record in the left table, it is still retained, while the columns from the left table contain blank or null values.&lt;/p&gt;

&lt;p&gt;Using the same &lt;code&gt;Sales&lt;/code&gt; and &lt;code&gt;Customers&lt;/code&gt; tables:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Sales                         Customers
┌─────────┬────────────┐      ┌────────────┬──────────────┐
│ SalesID │ CustomerID │      │ CustomerID │ CustomerName │
├─────────┼────────────┤      ├────────────┼──────────────┤
│ S001    │ C001       │      │ C001       │ Joseph Kamau       │
│ S002    │ C002       │      │ C002       │ Mitchell Faraja       │
│ S003    │ C003       │      │ C003       │ Joseph Kamau      │
└─────────┴────────────┘      │ C004       │ David        │
                              └────────────┴──────────────┘
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If we merge the tables using &lt;code&gt;CustomerID&lt;/code&gt; and select &lt;strong&gt;Right Outer&lt;/strong&gt;, every record from &lt;code&gt;Customers&lt;/code&gt; is retained.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Result

┌─────────┬────────────┬──────────────┐
│ SalesID │ CustomerID │ CustomerName │
├─────────┼────────────┼──────────────┤
│ S001    │ C001       │ Joseph Kamau       │
│ S002    │ C002       │ Mitchell Faraja       │
│ S003    │ C003       │ Joseph Kamau       │
│ null    │ C004       │ David        │
└─────────┴────────────┴──────────────┘
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;C004&lt;/code&gt; is retained because it exists in the right-hand &lt;code&gt;Customers&lt;/code&gt; table, even though there is no corresponding sales record. Since there is no matching record in &lt;code&gt;Sales&lt;/code&gt;, the &lt;code&gt;SalesID&lt;/code&gt; value is null.&lt;/p&gt;

&lt;p&gt;A Right Outer Join can therefore be useful when the &lt;strong&gt;right table is the table whose records must all be preserved&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;In practice, the same result could often be achieved by switching the order of the tables and using a Left Outer Join. Therefore, the important concept is not the word "right" itself, but understanding &lt;strong&gt;which table you want to preserve&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;In simple terms:&lt;/strong&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Right Outer = keep everything from the right table + matching records from the left table.&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  Full Outer Join
&lt;/h3&gt;

&lt;p&gt;A &lt;strong&gt;Full Outer Join&lt;/strong&gt; keeps all records from both the left and right tables. Where a matching value exists in both tables, the records are combined. Where a record exists in only one table, it is still retained and the columns from the other table contain blank or null values.&lt;/p&gt;

&lt;p&gt;Using the same &lt;code&gt;Sales&lt;/code&gt; and &lt;code&gt;Customers&lt;/code&gt; tables:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Sales                         Customers
┌─────────┬────────────┐      ┌────────────┬──────────────┐
│ SalesID │ CustomerID │      │ CustomerID │ CustomerName │
├─────────┼────────────┤      ├────────────┼──────────────┤
│ S001    │ C001       │      │ C001       │ Joseph Kamau       │
│ S002    │ C002       │      │ C002       │ Mitchell Faraja       │
│ S003    │ C003       │      │ C003       │ Joseph Kamau       │
│ S004    │ C005       │      │ C004       │ David        │
└─────────┴────────────┘      └────────────┴──────────────┘
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If we merge the tables using &lt;code&gt;CustomerID&lt;/code&gt; and select &lt;strong&gt;Full Outer&lt;/strong&gt;, every record from both tables is retained.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Result

┌─────────┬────────────┬──────────────┐
│ SalesID │ CustomerID │ CustomerName │
├─────────┼────────────┼──────────────┤
│ S001    │ C001       │ Joseph Kamau      │
│ S002    │ C002       │ Mitchell Faraja        │
│ S003    │ C003       │ Joseph Kamau       │
│ S004    │ C005       │ null         │
│ null    │ C004       │ David        │
└─────────┴────────────┴──────────────┘
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Here, &lt;code&gt;C001&lt;/code&gt;, &lt;code&gt;C002&lt;/code&gt; and &lt;code&gt;C003&lt;/code&gt; exist in both tables, so their information is combined. &lt;code&gt;C005&lt;/code&gt; exists only in &lt;code&gt;Sales&lt;/code&gt;, while &lt;code&gt;C004&lt;/code&gt; exists only in &lt;code&gt;Customers&lt;/code&gt;. Both are still retained because a Full Outer Join does not discard unmatched records.&lt;/p&gt;

&lt;p&gt;A Full Outer Join can therefore be useful when the goal is to &lt;strong&gt;identify all records across two datasets&lt;/strong&gt;, including records that do not have a match.&lt;/p&gt;

&lt;p&gt;For example, an analyst could use it to compare two datasets and identify customers that appear in one system but not the other.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;In simple terms:&lt;/strong&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Full Outer = keep everything from both tables, whether there is a match or not.&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  Inner Join
&lt;/h3&gt;

&lt;p&gt;An &lt;strong&gt;Inner Join&lt;/strong&gt; keeps only the records that have a matching value in &lt;strong&gt;both&lt;/strong&gt; tables. Any record that does not have a match is excluded from the merged result.&lt;/p&gt;

&lt;p&gt;Using the same &lt;code&gt;Sales&lt;/code&gt; and &lt;code&gt;Customers&lt;/code&gt; tables:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Sales                         Customers
┌─────────┬────────────┐      ┌────────────┬──────────────┐
│ SalesID │ CustomerID │      │ CustomerID │ CustomerName │
├─────────┼────────────┤      ├────────────┼──────────────┤
│ S001    │ C001       │      │ C001       │ Joseph Kamau       │
│ S002    │ C002       │      │ C002       │ Mitchell Faraja        │
│ S003    │ C003       │      │ C003       │ Joseph Kamau      │
│ S004    │ C005       │      │ C004       │ David        │
└─────────┴────────────┘      └────────────┴──────────────┘
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If the tables are merged using &lt;code&gt;CustomerID&lt;/code&gt; with an &lt;strong&gt;Inner Join&lt;/strong&gt;, only customers that appear in both tables are retained.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Result

┌─────────┬────────────┬──────────────┐
│ SalesID │ CustomerID │ CustomerName │
├─────────┼────────────┼──────────────┤
│ S001    │ C001       │ Joseph Kamau       │
│ S002    │ C002       │ Mitchell Faraja        │
│ S003    │ C003       │ Joseph Kamau       │
└─────────┴────────────┴──────────────┘
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;C005&lt;/code&gt; is removed because it exists in &lt;code&gt;Sales&lt;/code&gt; but not in &lt;code&gt;Customers&lt;/code&gt;. Similarly, &lt;code&gt;C004&lt;/code&gt; is not included because it exists in &lt;code&gt;Customers&lt;/code&gt; but has no matching record in &lt;code&gt;Sales&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;An Inner Join is useful when the analysis should only include records that exist in both datasets. For example, an analyst may want to analyse only sales transactions that can be matched to a valid customer record.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;In simple terms:&lt;/strong&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Inner Join = keep only the records that match in both tables.&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  Left Anti Join
&lt;/h3&gt;

&lt;p&gt;A &lt;strong&gt;Left Anti Join&lt;/strong&gt; returns only the records from the left table that &lt;strong&gt;do not have a matching record in the right table&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Using the same example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Sales                         Customers
┌─────────┬────────────┐      ┌────────────┬──────────────┐
│ SalesID │ CustomerID │      │ CustomerID │ CustomerName │
├─────────┼────────────┤      ├────────────┼──────────────┤
│ S001    │ C001       │      │ C001       │ Joseph Kamau      │
│ S002    │ C002       │      │ C002       │ Mitchell Faraja       │
│ S003    │ C003       │      │ C003       │ Joseph Kamau       │
│ S004    │ C005       │      │ C004       │ David        │
└─────────┴────────────┘      └────────────┴──────────────┘
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;When &lt;code&gt;Sales&lt;/code&gt; is the left table, a Left Anti Join checks which &lt;code&gt;CustomerID&lt;/code&gt; values in &lt;code&gt;Sales&lt;/code&gt; cannot be found in &lt;code&gt;Customers&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Result

┌─────────┬────────────┐
│ SalesID │ CustomerID │
├─────────┼────────────┤
│ S004    │ C005       │
└─────────┴────────────┘
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;C005&lt;/code&gt; is returned because it exists in &lt;code&gt;Sales&lt;/code&gt; but does not exist in &lt;code&gt;Customers&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;This type of join is particularly useful for &lt;strong&gt;data-quality checks&lt;/strong&gt;. For example, it can help an analyst identify sales transactions that refer to customer IDs that are missing from the customer master table.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;In simple terms:&lt;/strong&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Left Anti = records in the left table that have NO match in the right table.&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  Right Anti Join
&lt;/h3&gt;

&lt;p&gt;A &lt;strong&gt;Right Anti Join&lt;/strong&gt; returns only the records from the right table that &lt;strong&gt;do not have a matching record in the left table&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Using the same tables, &lt;code&gt;Customers&lt;/code&gt; is the right table. The join checks which customers do not appear in the &lt;code&gt;Sales&lt;/code&gt; table.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Result

┌────────────┬──────────────┐
│ CustomerID │ CustomerName │
├────────────┼──────────────┤
│ C004       │ David        │
└────────────┴──────────────┘
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;C004&lt;/code&gt; is returned because it exists in &lt;code&gt;Customers&lt;/code&gt; but has no corresponding record in &lt;code&gt;Sales&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;A Right Anti Join can therefore be useful for identifying records that exist in one dataset but are missing from another. For example, an analyst could use it to find registered customers who have not yet appeared in a sales dataset.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;In simple terms:&lt;/strong&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Right Anti = records in the right table that have NO match in the left table.&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Power Query Joins vs Power BI Relationships
&lt;/h2&gt;

&lt;p&gt;Power Query joins and Power BI relationships can both involve matching columns between tables, but they serve different purposes and happen at different stages of the Power BI workflow.&lt;/p&gt;

&lt;p&gt;The main difference is that a &lt;strong&gt;Power Query merge combines data&lt;/strong&gt;, while a &lt;strong&gt;Power BI relationship connects tables without physically combining them&lt;/strong&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Power Query Joins
&lt;/h3&gt;

&lt;p&gt;A Power Query merge is performed during the &lt;strong&gt;data preparation and transformation stage&lt;/strong&gt;. When two tables are merged, columns from one table can be brought into another based on matching values.&lt;/p&gt;

&lt;p&gt;For example, if the &lt;code&gt;Sales&lt;/code&gt; table contains &lt;code&gt;CustomerID&lt;/code&gt; but does not contain the customer's name, Power Query can merge it with the &lt;code&gt;Customers&lt;/code&gt; table using &lt;code&gt;CustomerID&lt;/code&gt; and bring &lt;code&gt;CustomerName&lt;/code&gt; into the resulting table.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Sales + Customers
       │
       │  Merge using CustomerID
       ▼
Combined table
       │
       ├── SalesID
       ├── CustomerID
       ├── CustomerName
       └── SalesAmount
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The result is a physically combined table containing columns from both sources.&lt;/p&gt;

&lt;h3&gt;
  
  
  Power BI Relationships
&lt;/h3&gt;

&lt;p&gt;A relationship is created after the data has been loaded into the Power BI model. Instead of combining the tables, Power BI keeps them separate and creates a connection between them using related columns.&lt;/p&gt;

&lt;p&gt;For example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;DimCustomer                 FactSales
┌──────────────┐            ┌──────────────┐
│ CustomerID   │  1      *  │ CustomerID   │
│ CustomerName │───────────►│ SalesAmount  │
│ City         │            │ Quantity     │
└──────────────┘            └──────────────┘
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The tables remain separate, but the relationship allows filters from &lt;code&gt;DimCustomer&lt;/code&gt; to affect the sales data in &lt;code&gt;FactSales&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;This is one of the main reasons relationships are important in a star schema. Customer information does not need to be copied into every sales transaction. Instead, the model connects the two tables through &lt;code&gt;CustomerID&lt;/code&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Key Differences
&lt;/h3&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Power Query Merge&lt;/th&gt;
&lt;th&gt;Power BI Relationship&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Happens during data preparation&lt;/td&gt;
&lt;td&gt;Happens in the data model&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Physically combines columns from tables&lt;/td&gt;
&lt;td&gt;Keeps tables separate&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Produces a new/expanded query result&lt;/td&gt;
&lt;td&gt;Creates a logical connection between tables&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Used to transform and prepare data&lt;/td&gt;
&lt;td&gt;Used for analysis, filtering and calculations&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Can increase the width of a table&lt;/td&gt;
&lt;td&gt;Keeps fact and dimension tables separate&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Uses join types such as Inner and Left Outer&lt;/td&gt;
&lt;td&gt;Uses relationship properties such as cardinality and filter direction&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h3&gt;
  
  
  When Should You Use a Merge?
&lt;/h3&gt;

&lt;p&gt;A merge is useful when information from two sources genuinely needs to become part of the same table.&lt;/p&gt;

&lt;p&gt;For example, an analyst may merge a product lookup table into a dataset during preparation when the product attributes are needed directly in the resulting table.&lt;/p&gt;

&lt;p&gt;However, merging every table into one large dataset is not always a good approach. Excessive merging can create wide tables, increase duplication and make the data model harder to maintain.&lt;/p&gt;

&lt;h3&gt;
  
  
  When Should You Use a Relationship?
&lt;/h3&gt;

&lt;p&gt;A relationship is generally preferable when the tables represent different types of information that should remain separate.&lt;/p&gt;

&lt;p&gt;For example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;DimCustomer ───────► FactSales ◄─────── DimProduct
     1                    *                   1
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Here, customer and product information belongs in their respective dimension tables, while sales transactions belong in the fact table. Relationships allow these tables to work together during analysis without physically combining them.&lt;/p&gt;

&lt;p&gt;This structure supports the star schema discussed earlier and allows the model to remain organised as more data is added.&lt;/p&gt;

&lt;h3&gt;
  
  
  Why Not Merge Everything?
&lt;/h3&gt;

&lt;p&gt;It may seem simpler to combine all the data into one large table, but doing so can introduce unnecessary repetition.&lt;/p&gt;

&lt;p&gt;For example, if the same customer makes 1,000 purchases, merging all of the customer's descriptive information into every sales record would repeat that information 1,000 times.&lt;/p&gt;

&lt;p&gt;Keeping the customer information in &lt;code&gt;DimCustomer&lt;/code&gt; and connecting it to &lt;code&gt;FactSales&lt;/code&gt; through a relationship avoids this unnecessary duplication.&lt;/p&gt;

&lt;p&gt;Therefore, the choice between a merge and a relationship depends on &lt;strong&gt;when and why the data needs to be combined&lt;/strong&gt;:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Use Power Query merges to prepare and transform data. Use Power BI relationships to connect separate tables for analysis.&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Recommended Power BI Model
&lt;/h2&gt;

&lt;p&gt;Based on the concepts discussed above, a &lt;strong&gt;star schema with one-to-many relationships and single-direction filtering&lt;/strong&gt; is generally the most suitable model for Power BI reporting and analytical workloads.&lt;/p&gt;

&lt;p&gt;A typical e-commerce model could be structured as follows:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;                         ┌─────────────────┐
                         │     DimDate     │
                         └────────┬────────┘
                                  │ 1
                                  │
                                  │ *
┌─────────────────┐        ┌─────▼───────────┐        ┌─────────────────┐
│  DimCustomer    │ 1    * │    FactSales    │ *    1 │   DimProduct    │
├─────────────────┤────────►│                 │◄────────├─────────────────┤
│ CustomerKey     │         │ CustomerKey     │         │ ProductKey      │
│ CustomerName    │         │ ProductKey      │         │ ProductName     │
│ City            │         │ DateKey         │         │ Category        │
│ Country         │         │ Quantity        │         │ Brand           │
└─────────────────┘         │ Unit Price     │         └─────────────────┘
                            └─────────────────┘
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In this model, &lt;code&gt;FactSales&lt;/code&gt; sits at the centre and contains the business transactions and numeric values being analysed. The dimension tables provide the descriptive context used to filter and group those transactions.&lt;/p&gt;

&lt;h3&gt;
  
  
  Why Use a Star Schema?
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Simplicity:&lt;/strong&gt;&lt;br&gt;
Each dimension connects directly to the fact table, making the model easier to understand and navigate. Report developers can quickly identify where customer, product and date information is stored.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Performance:&lt;/strong&gt;&lt;br&gt;
A simpler relationship structure reduces unnecessary paths between tables and can allow Power BI to process analytical queries more efficiently. This becomes increasingly important as the amount of data grows.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Simpler DAX:&lt;/strong&gt;&lt;br&gt;
A clear separation between fact and dimension tables makes it easier to write and understand measures. For example, a measure calculating total sales can operate on the sales values in &lt;code&gt;FactSales&lt;/code&gt;, while dimensions provide the context in which those sales are analysed.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Effective filter propagation:&lt;/strong&gt;&lt;br&gt;
Single-direction relationships allow filters to normally flow from dimensions to the fact table. For example, selecting a product category in &lt;code&gt;DimProduct&lt;/code&gt; filters the corresponding records in &lt;code&gt;FactSales&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Reduced unnecessary duplication:&lt;/strong&gt;&lt;br&gt;
Customer and product attributes are stored in their respective dimension tables rather than being repeated throughout every sales transaction. This keeps the fact table focused on the events and measurements being analysed.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Readability and maintainability:&lt;/strong&gt;&lt;br&gt;
Each table has a clear purpose. If a new customer attribute is required, it can be added to &lt;code&gt;DimCustomer&lt;/code&gt;, while a new sales measure can be added to &lt;code&gt;FactSales&lt;/code&gt;. This makes the model easier to maintain as reporting requirements change.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Scalability:&lt;/strong&gt;&lt;br&gt;
A star schema can accommodate additional dimensions as the reporting requirements grow. For example, a &lt;code&gt;DimLocation&lt;/code&gt; or &lt;code&gt;DimSalesperson&lt;/code&gt; table can be added and connected directly to &lt;code&gt;FactSales&lt;/code&gt; without redesigning the entire model.&lt;/p&gt;
&lt;h3&gt;
  
  
  Recommended Relationship Design
&lt;/h3&gt;

&lt;p&gt;For this model, the preferred relationship pattern would be &lt;strong&gt;one-to-many (1:*)&lt;/strong&gt;, with each dimension on the one side and the fact table on the many side.&lt;/p&gt;

&lt;p&gt;The relationships would also normally use &lt;strong&gt;single-direction filtering&lt;/strong&gt;, allowing filters to flow from the dimensions towards the fact table.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;DimCustomer ──►
DimProduct  ──► FactSales
DimDate     ──►
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This approach provides predictable filter behaviour and avoids introducing unnecessary bidirectional relationships that could create ambiguous filter paths.&lt;/p&gt;

&lt;h3&gt;
  
  
  Why Not Use a Snowflake Schema?
&lt;/h3&gt;

&lt;p&gt;A snowflake schema can be appropriate in some situations, particularly where dimensions naturally contain several levels of hierarchy. However, for many Power BI reporting models, the additional tables and relationships can make the model more complicated than necessary.&lt;/p&gt;

&lt;p&gt;For the e-commerce example used throughout this article, keeping product attributes such as category and brand within &lt;code&gt;DimProduct&lt;/code&gt; provides a simpler structure than creating separate tables for each level of the product hierarchy.&lt;/p&gt;

&lt;p&gt;Therefore, the recommended model is a &lt;strong&gt;star schema with one-to-many relationships and predominantly single-direction filtering&lt;/strong&gt;. It provides a practical balance between performance, simplicity, usability, scalability and maintainability.&lt;/p&gt;

&lt;h2&gt;
  
  
  Comparison of Flat Table, Star Schema and Snowflake Schema
&lt;/h2&gt;

&lt;p&gt;The three modelling approaches discussed above can be compared based on their structure, complexity, performance, flexibility and suitability for Power BI reporting.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Aspect&lt;/th&gt;
&lt;th&gt;Flat Table&lt;/th&gt;
&lt;th&gt;Star Schema&lt;/th&gt;
&lt;th&gt;Snowflake Schema&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Structure&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Most data stored in one table&lt;/td&gt;
&lt;td&gt;One central fact table connected directly to dimensions&lt;/td&gt;
&lt;td&gt;Fact table connected to dimensions that may be further split into related tables&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Data redundancy&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;High, because descriptive information may be repeated&lt;/td&gt;
&lt;td&gt;Lower, because descriptive data is stored in dimensions&lt;/td&gt;
&lt;td&gt;Generally lower because dimensions are more normalized&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Number of tables&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Usually one large table&lt;/td&gt;
&lt;td&gt;Fact table + dimension tables&lt;/td&gt;
&lt;td&gt;Fact table + multiple related dimension/hierarchy tables&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Model complexity&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Simple at first, but can become difficult to manage as data grows&lt;/td&gt;
&lt;td&gt;Relatively simple and easy to understand&lt;/td&gt;
&lt;td&gt;More complex because of additional tables and relationships&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Relationships&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Few or none within the model&lt;/td&gt;
&lt;td&gt;Mainly one-to-many relationships between dimensions and facts&lt;/td&gt;
&lt;td&gt;More relationships because dimensions may connect to other dimension tables&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Filter propagation&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Limited need for relationship-based filtering&lt;/td&gt;
&lt;td&gt;Clear and predictable from dimensions to fact&lt;/td&gt;
&lt;td&gt;Can involve more relationship paths&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;DAX and analysis&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Can become harder to manage as the table grows&lt;/td&gt;
&lt;td&gt;Generally simpler because the model has a clear structure&lt;/td&gt;
&lt;td&gt;Can require more complex relationships and calculations&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Scalability&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Less suitable as data and reporting requirements grow&lt;/td&gt;
&lt;td&gt;Highly suitable for growing analytical models&lt;/td&gt;
&lt;td&gt;Can scale, but complexity increases with additional normalized tables&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Maintainability&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Changes may affect a large table&lt;/td&gt;
&lt;td&gt;Easier because each table has a clear purpose&lt;/td&gt;
&lt;td&gt;More difficult because changes may involve several related tables&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Typical use&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Small datasets, simple analysis, spreadsheets&lt;/td&gt;
&lt;td&gt;Business intelligence and Power BI reporting&lt;/td&gt;
&lt;td&gt;Complex or highly normalized data structures&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Recommended for Power BI reporting?&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Usually not for larger analytical models&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;Yes generally preferred&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Useful in specific scenarios&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;From the comparison, the &lt;strong&gt;star schema provides a strong balance between simplicity, performance, scalability and maintainability&lt;/strong&gt;. A flat table may be convenient when working with a small dataset, but repeated information and increasing table size can make it less suitable for a larger analytical model.&lt;/p&gt;

&lt;p&gt;A snowflake schema can reduce redundancy and may be useful when dealing with complex hierarchies or highly normalized source data. However, it introduces additional tables and relationships that can make the model harder to understand and maintain.&lt;/p&gt;

&lt;p&gt;For the e-commerce example used throughout this article, the &lt;strong&gt;star schema is therefore the preferred approach&lt;/strong&gt; because it keeps the fact table focused on transactions while dimensions provide the descriptive information needed for analysis.&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;Data modelling is an important part of building effective Power BI reports because it determines how data is organised, connected and used for analysis. A well-designed model makes it easier to create reliable calculations, apply filters correctly and maintain the report as the amount of data and reporting requirements increase.&lt;/p&gt;

&lt;p&gt;The comparison between flat tables, star schemas and snowflake schemas shows that each approach has its place. A flat table can be useful for simple datasets, while a snowflake schema can be appropriate when dealing with more complex or highly normalized data structures. However, for most Power BI analytical and reporting scenarios, a &lt;strong&gt;star schema provides a practical balance between simplicity, performance, scalability and maintainability&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Understanding the difference between fact and dimension tables is also important when building this type of model. Fact tables contain the events and measurements being analysed, while dimension tables provide the descriptive context used to filter and group those events. Defining the grain of the fact table and using appropriate primary and foreign keys helps maintain a consistent and reliable model.&lt;/p&gt;

&lt;p&gt;Relationships then connect these tables and allow filters to move through the model. In a typical star schema, one-to-many relationships with single-direction filtering provide a clear and predictable structure. Active and inactive relationships can also be used when different date or analytical perspectives are required.&lt;/p&gt;

&lt;p&gt;Power Query joins serve a different purpose. They are useful during the data preparation stage when information from different sources needs to be combined, filtered or compared. Power BI relationships, on the other hand, allow separate tables to work together during analysis without physically combining their data.&lt;/p&gt;

&lt;p&gt;Overall, effective Power BI modelling is not simply about creating relationships between tables. It is about designing a structure that reflects the business data clearly and allows that data to be analysed efficiently. For the e-commerce scenario used throughout this article, a &lt;strong&gt;star schema with one-to-many relationships and predominantly single-direction filtering&lt;/strong&gt; provides the most suitable foundation for building a clear, scalable and maintainable Power BI reporting model.&lt;/p&gt;

</description>
      <category>analytics</category>
      <category>data</category>
      <category>database</category>
    </item>
    <item>
      <title>Building an Interactive Excel Dashboard for E-commerce Product Analysis: A Case Study of Jumia Products</title>
      <dc:creator>Alex Majale</dc:creator>
      <pubDate>Sun, 06 Sep 2026 12:02:19 +0000</pubDate>
      <link>https://dev.to/alex_majale_d64efa6d81883/building-an-interactive-excel-dashboard-for-e-commerce-product-analysis-a-case-study-of-jumia-8o</link>
      <guid>https://dev.to/alex_majale_d64efa6d81883/building-an-interactive-excel-dashboard-for-e-commerce-product-analysis-a-case-study-of-jumia-8o</guid>
      <description>&lt;p&gt;If you've ever wondered what actually happens between "I have a spreadsheet full of messy data" and "I have a dashboard I'd be proud to show someone," this article walks through that whole journey, step by step, using a real example.&lt;/p&gt;

&lt;h2&gt;
  
  
  1. Project Introduction and Objective
&lt;/h2&gt;

&lt;p&gt;Online stores discount things constantly. But here's a question that's easy to assume the answer to and actually get wrong: &lt;em&gt;Does a bigger discount get you more customer interest?&lt;/em&gt;&lt;br&gt;
The goal wasn't just "make some charts." It was to build something a manager could actually open and trust because every number on it either comes from a live formula or is clearly an assumption.&lt;/p&gt;
&lt;h2&gt;
  
  
  2. Dataset and Business Questions
&lt;/h2&gt;

&lt;p&gt;The raw data is : 116 rows, 6 columns.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Column&lt;/th&gt;
&lt;th&gt;What it holds&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Product&lt;/td&gt;
&lt;td&gt;The product&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Current price&lt;/td&gt;
&lt;td&gt;The price you'd actually pay&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;old price&lt;/td&gt;
&lt;td&gt;The original price before the discount&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Discount&lt;/td&gt;
&lt;td&gt;The advertised discount&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Review&lt;/td&gt;
&lt;td&gt;Number of customer reviews&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Ratingd&lt;/td&gt;
&lt;td&gt;rating&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F629cziw14kyekqwfv41u.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F629cziw14kyekqwfv41u.png" alt=" " width="800" height="438"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Business questions:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;whether larger discounts are associated with more reviews&lt;/li&gt;
&lt;li&gt;whether highly rated products attract stronger engagement&lt;/li&gt;
&lt;li&gt;whether price and rating move together&lt;/li&gt;
&lt;li&gt;which products perform best based on ratings and reviews&lt;/li&gt;
&lt;li&gt;which products may need a different pricing or marketing strategy.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;
  
  
  3. Initial Data-Quality Audit
&lt;/h2&gt;

&lt;p&gt;I audited the raw data for problems. Here's what I actually found:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;58 rows&lt;/strong&gt; had no value in &lt;code&gt;Review&lt;/code&gt; or &lt;code&gt;Ratingd&lt;/code&gt; at all.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;58 rows&lt;/strong&gt; had negative numbers in the &lt;code&gt;Review&lt;/code&gt; column. Reviews are a count of people, so a negative count is impossible.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;3 rows&lt;/strong&gt; were exact duplicates.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;6 different product names&lt;/strong&gt; appeared more than once, but with different prices.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;1 row&lt;/strong&gt; had a price range instead of a single value.&lt;/li&gt;
&lt;li&gt;Prices were stored as &lt;strong&gt;text&lt;/strong&gt;, with a currency prefix.&lt;/li&gt;
&lt;li&gt;Ratings were stored as &lt;strong&gt;text&lt;/strong&gt;.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;
  
  
  4. Cleaning and Preparation Decisions
&lt;/h2&gt;

&lt;p&gt;Every cleaning decision went into a documented "data dictionary" &lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Issue&lt;/th&gt;
&lt;th&gt;Rows affected&lt;/th&gt;
&lt;th&gt;Decision&lt;/th&gt;
&lt;th&gt;Reason&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Negative review counts&lt;/td&gt;
&lt;td&gt;58 rows&lt;/td&gt;
&lt;td&gt;Converted to positive&lt;/td&gt;
&lt;td&gt;A review count can't be negative&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Exact duplicate rows&lt;/td&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;td&gt;Removed&lt;/td&gt;
&lt;td&gt;They were duplicates&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Duplicate product names, different prices&lt;/td&gt;
&lt;td&gt;6&lt;/td&gt;
&lt;td&gt;Kept as separate rows&lt;/td&gt;
&lt;td&gt;interpreted as different types of the same product&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;One price stored as a range&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;Replaced with the midpoint&lt;/td&gt;
&lt;td&gt;A single number was required for any calculation to work&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Prices stored as text with currency symbols&lt;/td&gt;
&lt;td&gt;113&lt;/td&gt;
&lt;td&gt;Converted to numbers&lt;/td&gt;
&lt;td&gt;Can't calculate on text&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Ratings stored as &lt;code&gt;"4.5 out of 5"&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;58&lt;/td&gt;
&lt;td&gt;Extracted the numeric &lt;code&gt;4.5&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;Text can't be calculated&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fx0miu4uu5t7pz6kmdddq.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fx0miu4uu5t7pz6kmdddq.png" alt=" " width="800" height="436"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h2&gt;
  
  
  5. Excel Formulas and Enrichment Fields
&lt;/h2&gt;

&lt;p&gt;This is the part that turns "a cleaned spreadsheet" into "a dataset you can actually analyze." &lt;br&gt;
Breaking down what these do:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;&lt;code&gt;Rating Status&lt;/code&gt;&lt;/strong&gt; &lt;code&gt;=IF(OR(F2&amp;lt;0,F2&amp;gt;5,ISBLANK(F2)),"Check rating","OK")&lt;/code&gt;&lt;br&gt;
Rating must be between 0 and 5. If it isn't (or it's blank), this flags it instead of silently trusting the data.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;&lt;code&gt;Calculated Discount&lt;/code&gt;&lt;/strong&gt; &lt;code&gt;=IFERROR((C2-B2)/C2,"")&lt;/code&gt;&lt;br&gt;
Instead of trusting the &lt;em&gt;advertised&lt;/em&gt; discount, I recalculated it independently from the old and new prices. &lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;&lt;code&gt;Discount Check&lt;/code&gt;&lt;/strong&gt; &lt;code&gt;=IF(OR(D2="",K2=""),"Missing",IF(ABS(D2-K2)&amp;gt;2%,"Check Discount","OK"))&lt;/code&gt; compares the advertised discount to the calculated one, and flags anything more than 2 percentage points apart.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;&lt;code&gt;Rating Category&lt;/code&gt;&lt;/strong&gt; &lt;code&gt;=IF(F2="","Missing",IF(F2&amp;lt;3,"Poor",IF(F2&amp;lt;=4.5,"Average","Excellent")))&lt;/code&gt; Rating into "Poor" (below 3), "Average" (3–4.5), or "Excellent" (above 4.5), so I can group and count products instead of comparing 113 individual decimals.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I used the same pattern for &lt;code&gt;Discount Category&lt;/code&gt; (Low/Medium/High) &lt;code&gt;=IF(D2="","Missing",IF(D2&amp;lt;20%,"Low Discount",IF(D2&amp;lt;=40%,"Medium Discount","High Discount")))&lt;/code&gt; and &lt;code&gt;Price Category&lt;/code&gt; (Low/Medium/High) &lt;code&gt;=IF(B2="","Missing",IF(B2&amp;lt;=Price_Q1,"Low Price",IF(B2&amp;lt;=Price_Q3,"Medium Price","High Price")))&lt;/code&gt; , the price Categories are based on the dataset's own &lt;strong&gt;quartiles&lt;/strong&gt;, calculated with:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Price_Q1 = QUARTILE.INC(tblProducts[Current Price], 1)   → KSh 493
Price_Q3 = QUARTILE.INC(tblProducts[Current Price], 3)   → KSh 1,669.50
Review_Q3 = QUARTILE.INC(tblProducts[Review], 3)         → 14 reviews
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;I stored these as &lt;strong&gt;named ranges&lt;/strong&gt; so every formula in the workbook could reference &lt;code&gt;Price_Q1&lt;/code&gt; instead of a hardcoded number.&lt;br&gt;
&lt;strong&gt;Engagement flags&lt;/strong&gt; &lt;code&gt;=IF(E2="","Missing",IF(E2&amp;gt;=Review_Q3,"Strong Engagement","Below Threshold"))&lt;/code&gt; so that i can group them with the dataset review threshold.&lt;/p&gt;

&lt;p&gt;Finally, I layered on a few &lt;strong&gt;compound flags&lt;/strong&gt; that combine two conditions at once:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;High Discount + Low Rating&lt;/code&gt; &lt;code&gt;=IF(OR(D2="",E2=""),"Missing",IF(AND(D2&amp;gt;40%,F2&amp;lt;3),"Flag",""))&lt;/code&gt; discount over 40% &lt;em&gt;and&lt;/em&gt; rating under 3&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;High Discount + Low Engagement&lt;/code&gt; &lt;code&gt;=IF(OR(D2="",E2=""),"Missing",IF(AND(D2&amp;gt;40%,E2&amp;lt;Review_Q3),"Flag",""))&lt;/code&gt; discount over 40% &lt;em&gt;and&lt;/em&gt; reviews below the 75th percentile&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;Many Reviews + Average Rating&lt;/code&gt; &lt;code&gt;=IF(AND(E2&amp;gt;=Review_Q3,F2&amp;gt;=3,F2&amp;lt;=4.5),"Flag","")&lt;/code&gt; heavily reviewed but only "Average," not "Excellent"&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  6. PivotTable and Analysis Workflow
&lt;/h2&gt;

&lt;p&gt;With every product tagged and categorized, PivotTables became genuinely simple.&lt;/p&gt;

&lt;p&gt;Here's one of the  PivotTables:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fy9il9s1ypiwgko6jqw0d.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fy9il9s1ypiwgko6jqw0d.png" alt=" " width="799" height="438"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;PivotTable feeding a chart:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F6oxj802ywo838nr5zacz.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F6oxj802ywo838nr5zacz.png" alt=" " width="800" height="436"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;I built seven of these PivotTable/PivotChart pairs in total — rating mix, discount mix, price vs. rating, engagement vs. discount, and three "Top 10" rankings (by rating, by reviews, by discount).&lt;/p&gt;

&lt;p&gt;Alongside the PivotTables, I ran descriptive statistics and correlation checks directly on the &lt;code&gt;Cleaned Data&lt;/code&gt; table using &lt;code&gt;AVERAGE&lt;/code&gt;, &lt;code&gt;SUM&lt;/code&gt;, &lt;code&gt;MAX&lt;/code&gt;/&lt;code&gt;MIN&lt;/code&gt;, and &lt;code&gt;CORREL&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  7. Dashboard Design and Slicer Connections
&lt;/h2&gt;

&lt;p&gt;Everything comes together on a single dashboard sheet: a title bar, five KPI cards across the top, and nine charts.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fgq01pbnswdxwa8zmnnuh.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fgq01pbnswdxwa8zmnnuh.png" alt=" " width="799" height="435"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  8. Key Findings
&lt;/h2&gt;

&lt;p&gt;Here's the "Business Insights" sheet I built to summarize what the data actually showed &lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fvjydyfz84yi7lfh2tffg.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fvjydyfz84yi7lfh2tffg.png" alt=" " width="800" height="436"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;In plain language:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;The catalog is aggressively discounted.&lt;/strong&gt; 55.4% of products (62 of 112) are discounted more than 40%.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;But bigger discounts don't buy more attention.&lt;/strong&gt; Products discounted 20–40% average 15.3 reviews each &lt;em&gt;more&lt;/em&gt; than the heavily-discounted group's &lt;strong&gt;11.1&lt;/strong&gt; average, and clearly more than the lightly-discounted group's 9.5. The correlation between discount size and review count across the whole catalog is essentially flat (r ≈ -0.14).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Rating and popularity are basically unrelated.&lt;/strong&gt; A product's star rating barely correlates with how many people reviewed it (r ≈ 0.06).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Price has only a weak relationship with rating.&lt;/strong&gt; Higher-priced products rate slightly better on average (4.08 vs. 3.64 for the cheapest band), but the relationship is too weak to call a real trend (r ≈ 0.11).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;One product is a real warning sign:&lt;/strong&gt; a cordless vacuum cleaner has the most reviews in the entire dataset (69) but only a 2.8-star rating.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  9. Business Recommendations
&lt;/h2&gt;

&lt;p&gt;Based on what the data actually supports:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Don't assume "discount deeper" means "sell more."&lt;/strong&gt; Test the 20–40% discount band deliberately it's where engagement was strongest in this dataset.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Audit high-visibility, low-rating listings first.&lt;/strong&gt; They're seen by the most people, so any quality or accuracy issue there does the most damage.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Invest in listing quality over price cuts.&lt;/strong&gt; Since price alone barely predicts rating, better photos, descriptions, and accurate specs may do more for perception than another 5% off.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Chase more reviews on unrated listings&lt;/strong&gt;, not fewer discounts. Half the catalog (55 of 112 products) has no rating or review at all.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  10. Limitations and Lessons Learned
&lt;/h2&gt;

&lt;p&gt;Being upfront about what this analysis &lt;em&gt;can't&lt;/em&gt; tell you is just as important as the findings themselves:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;A review count isn't a sales figure.&lt;/strong&gt; Engagement ≠ conversion, and nothing here should be read as a profit or revenue claim.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;One number was a judgment call&lt;/strong&gt;, not a fact: the single price-range row that I replaced with a midpoint.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Cleaning and documentation take longer than the "real" analysis, and that's normal.&lt;/strong&gt; Most of the time on this workbook went into deciding what a negative review count means, what to do with a price range, and how to label the 55 products with no rating.&lt;/p&gt;

&lt;h2&gt;
  
  
  11. Links
&lt;/h2&gt;

&lt;p&gt;[&lt;a href="https://github.com/majalealex-ux/Jumia-Product-Performance-Dashboard" rel="noopener noreferrer"&gt;https://github.com/majalealex-ux/Jumia-Product-Performance-Dashboard&lt;/a&gt;]&lt;/p&gt;

</description>
      <category>beginners</category>
      <category>data</category>
      <category>datascience</category>
    </item>
    <item>
      <title>Getting Started with Excel for Data Analytics: From Basics to Data Cleaning</title>
      <dc:creator>Alex Majale</dc:creator>
      <pubDate>Sat, 29 Aug 2026 05:06:33 +0000</pubDate>
      <link>https://dev.to/alex_majale_d64efa6d81883/getting-started-with-excel-for-data-analytics-from-basics-to-data-cleaning-upi-3n7b</link>
      <guid>https://dev.to/alex_majale_d64efa6d81883/getting-started-with-excel-for-data-analytics-from-basics-to-data-cleaning-upi-3n7b</guid>
      <description>&lt;h1&gt;
  
  
  Introduction
&lt;/h1&gt;

&lt;p&gt;We interact with data almost every day. But having data is one thing, and having clean, usable data is another. This is where Excel comes in. It can be a good starting point for cleaning, organising, and analysing data.&lt;br&gt;
Excel is an electronic spreadsheet program that allows users to organize, calculate, format, and analyze data.&lt;br&gt;
Data cleaning is the process of identifying and correcting errors, missing values, duplicates, and inconsistent formats in raw data sets.&lt;/p&gt;

&lt;h2&gt;
  
  
  Basics of Excel
&lt;/h2&gt;

&lt;p&gt;The first thing you'll see once you open Excel is rows, columns, and cells.&lt;br&gt;
A row runs horizontally and is labeled with numbers (1, 2, 3), while a column runs vertically and is labeled with letters (A, B, C)&lt;br&gt;
A cell is the individual rectangular box formed by the intersection of a vertical column and a horizontal row.&lt;br&gt;
These rows, columns, and cells are contained within a worksheet.&lt;br&gt;
One or more worksheets form a workbook.&lt;br&gt;
The image below contains an example of an Excel worksheet with its features;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F342cm5br3macm1modr19.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F342cm5br3macm1modr19.jpg" alt=" " width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Inside the worksheet, there is basic information I need to know, such as;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Sorting which is the process of rearranging your rows of data into a specific order based on the values in one or more columns. This includes arranging text from A to Z or Z to A, arranging numbers from smallest to largest or largest to smallest, and arranging dates and times from oldest to newest or newest to oldest.&lt;/li&gt;
&lt;li&gt;Filtering a tool that hides rows you do not want to see so you can focus only on the data that matches your rules.&lt;/li&gt;
&lt;li&gt;Basic formulas, where I learned that every formula has to begin with an equals sign (=). These formulas include addition, such as &lt;code&gt;=1+1&lt;/code&gt; or &lt;code&gt;=A1+B1&lt;/code&gt;, and subtraction, such as &lt;code&gt;=2-1&lt;/code&gt; or &lt;code&gt;=A1-B1&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Formatting which is the process of changing the visual appearance of data in a spreadsheet to make it easier to read and understand without changing the actual data values.&lt;/p&gt;
&lt;h2&gt;
  
  
  Data Cleaning
&lt;/h2&gt;

&lt;p&gt;I learnt some basic data cleaning techniques such as; &lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Removing duplicates&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Handling missing values&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Removing extra spaces with &lt;code&gt;TRIM()&lt;/code&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Standardizing text with &lt;code&gt;UPPER()&lt;/code&gt;, &lt;code&gt;LOWER()&lt;/code&gt;,&lt;code&gt;PROPER()&lt;/code&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Fixing inconsistent dates &lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;To understand how data cleaning works in Excel, I used a small example containing employee records. At first, the data looked  normal, but a closer look revealed some errors.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Employee Name&lt;/th&gt;
&lt;th&gt;Department&lt;/th&gt;
&lt;th&gt;Location&lt;/th&gt;
&lt;th&gt;Hire Date&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;john kamau&lt;/td&gt;
&lt;td&gt;Finance&lt;/td&gt;
&lt;td&gt;Nairobi&lt;/td&gt;
&lt;td&gt;12/01/2024&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Jane Wanjiku&lt;/td&gt;
&lt;td&gt;HR&lt;/td&gt;
&lt;td&gt;nairobi&lt;/td&gt;
&lt;td&gt;15/01/2024&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;John Kamau&lt;/td&gt;
&lt;td&gt;Finance&lt;/td&gt;
&lt;td&gt;Nairobi&lt;/td&gt;
&lt;td&gt;12/13/2024&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;brian Otieno&lt;/td&gt;
&lt;td&gt;IT&lt;/td&gt;
&lt;td&gt;NAIROBI&lt;/td&gt;
&lt;td&gt;18/01/2024&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Mary Akinyi&lt;/td&gt;
&lt;td&gt;HR&lt;/td&gt;
&lt;td&gt;Kisumu&lt;/td&gt;
&lt;td&gt;20/01/2024&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h3&gt;
  
  
  1. Removing duplicates
&lt;/h3&gt;

&lt;p&gt;The first issue I noticed was that &lt;strong&gt;John Kamau appeared twice with the same information&lt;/strong&gt;. Duplicate records can affect analysis by making some results appear higher than they actually are.&lt;/p&gt;

&lt;p&gt;I used Excel's &lt;em&gt;Conditional formatting&lt;/em&gt; feature to identify and remove the repeated record.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. Standardising text
&lt;/h3&gt;

&lt;p&gt;The Location column also contained different versions of the same location:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;code&gt;Nairobi&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;nairobi&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;NAIROBI&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Although they refer to the same place, Excel can treat them as different values when analysing the data.&lt;/p&gt;

&lt;p&gt;I standardised the entries so that they followed the same format.&lt;/p&gt;

&lt;h3&gt;
  
  
  3. Checking dates
&lt;/h3&gt;

&lt;p&gt;The Hire Date column also needs attention. Dates may appear in different formats depending on how the data was entered. I checked that the values were recognised as actual dates and then applied a consistent date format. Such as correcting the 12/13/2024 to 12/01/2024.&lt;/p&gt;

&lt;h3&gt;
  
  
  4. Fixing the Word format
&lt;/h3&gt;

&lt;p&gt;The employee name format is in improper format with words such as john kamau in lower cases instead of proper cases. So i used the formula &lt;code&gt;=PROPER(A2)&lt;/code&gt; to change the format to John Kamau &lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;I initially thought cleaning data meant simply removing blanks and duplicates. I learned that consistency is equally important, especially with dates, categories, and text.&lt;/p&gt;

</description>
      <category>beginners</category>
      <category>dataanalytics</category>
      <category>data</category>
    </item>
    <item>
      <title>My First GitHub Project: From a Local Folder to Github Using Git and SSH</title>
      <dc:creator>Alex Majale</dc:creator>
      <pubDate>Thu, 20 Aug 2026 10:09:58 +0000</pubDate>
      <link>https://dev.to/alex_majale_d64efa6d81883/my-first-github-project-from-a-local-folder-to-github-using-git-and-ssh-8e8</link>
      <guid>https://dev.to/alex_majale_d64efa6d81883/my-first-github-project-from-a-local-folder-to-github-using-git-and-ssh-8e8</guid>
      <description>&lt;h1&gt;
  
  
  Introduction
&lt;/h1&gt;

&lt;p&gt;When I first heard the words Git and GitHub, I actually thought that git was the short form of GitHub, only to realise that they are two different things.&lt;br&gt;
Git is free, open-source version control software used by programmers to track code changes and collaborate. Git is a version control system that runs on my computer. GitHub, on the other hand, is a cloud-based web platform where computer programmers store, share, and work together on software code. &lt;br&gt;
This article explains how I created a local repository and pushed the project to GitHub using Git and SSH.&lt;/p&gt;

&lt;h2&gt;
  
  
  What Requirements I Needed
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;A GitHub account&lt;/li&gt;
&lt;li&gt;Git installed on the Computer&lt;/li&gt;
&lt;li&gt;An SSH Key configured with GitHub&lt;/li&gt;
&lt;li&gt;Visual Studio Code installed on the Computer &lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  How I Did It
&lt;/h2&gt;

&lt;p&gt;I started by opening GitBash on my desktop, then typed &lt;code&gt;ls&lt;/code&gt; to see what my desktop contained.&lt;br&gt;
The &lt;code&gt;ls&lt;/code&gt; command means list, and it is used to list the contents of a directory.&lt;br&gt;
Next, I created a folder called My First Project.&lt;br&gt;
using &lt;code&gt;mkdir "My First Project"&lt;/code&gt;&lt;br&gt;
&lt;code&gt;mkdir&lt;/code&gt;means make directory and is used to create a new folder.&lt;br&gt;
I then used &lt;code&gt;cd "My First Project"&lt;/code&gt; to enter the folder.&lt;br&gt;
&lt;code&gt;cd&lt;/code&gt; means change directory and is used to open folders.&lt;br&gt;
While in the My First Project folder, I created another folder called &lt;strong&gt;Data&lt;/strong&gt;.&lt;br&gt;
Using &lt;code&gt;mkdir Data&lt;/code&gt;&lt;br&gt;
Then I created a README file using &lt;code&gt;touch README.md&lt;/code&gt;&lt;br&gt;
&lt;code&gt;touch&lt;/code&gt; is used to create an empty file&lt;br&gt;
So to add information to the README file I used&lt;br&gt;
&lt;code&gt;echo "# My First Project"&amp;gt; README.md&lt;/code&gt;&lt;br&gt;
&lt;code&gt;echo&lt;/code&gt; means to output or display text, data, or variable values directly to a screen, terminal, or log file.&lt;br&gt;
The # is used to show that it is a heading.&lt;br&gt;
I then generated practice data and copied it into the &lt;strong&gt;Data&lt;/strong&gt; folder&lt;br&gt;
I then used &lt;code&gt;echo "This is practice data on Hardware sales"&amp;gt;&amp;gt; README.md&lt;/code&gt;&lt;br&gt;
This time I used &amp;gt;&amp;gt; so that I could not overwrite the first line in the README file.&lt;br&gt;
I then used &lt;code&gt;cat README.md&lt;/code&gt; to see the contents of the README file.&lt;br&gt;
cat is a standard command used to read, print, combine, and create text files directly from the terminal.&lt;br&gt;
I then confirmed the contents of the Data folder using &lt;code&gt;ls Data&lt;/code&gt;&lt;br&gt;
I keyed in &lt;code&gt;git init&lt;/code&gt;&lt;br&gt;
&lt;code&gt;git init&lt;/code&gt;  stands for "Git Initialize," and it is the command used to create a brand-new, empty Git repository.&lt;br&gt;
Then &lt;code&gt;git status&lt;/code&gt;&lt;br&gt;
&lt;code&gt;git status&lt;/code&gt; shows the current state of your repository's working directory and staging area. It tells you which files have been changed, which ones are prepared for your next save point (commit), and which ones Git is completely ignoring.&lt;br&gt;
Then ran &lt;code&gt;git add .&lt;/code&gt;&lt;br&gt;
&lt;code&gt;git add&lt;/code&gt; is the command used to save your code changes into a temporary preparation zone.&lt;br&gt;
Then I ran &lt;code&gt;git commit -m "Add My First Project"&lt;/code&gt;&lt;br&gt;
&lt;code&gt;git commit&lt;/code&gt; is a save point, or snapshot, of your project's files at a specific moment in time.&lt;br&gt;
I then ran &lt;code&gt;git log&lt;/code&gt;&lt;br&gt;
&lt;code&gt;git log&lt;/code&gt; is a command-line tool used to view the history of changes (commits) made to a code repository.&lt;br&gt;
Then &lt;code&gt;git remote add origin[SSH link]&lt;/code&gt;&lt;br&gt;
The SSH link is found on GitHub, where you copy it from the repository you created.&lt;br&gt;
Then I ran &lt;code&gt;git remote -v&lt;/code&gt;&lt;br&gt;
Finally, I ran &lt;code&gt;git push -u origin main&lt;/code&gt;&lt;br&gt;
&lt;code&gt;git push&lt;/code&gt; uploads your local repository commits to a remote repository.&lt;br&gt;
After this, the project was uploaded to my GitHub. &lt;/p&gt;

&lt;h1&gt;
  
  
  Conclusion
&lt;/h1&gt;

&lt;p&gt;I was able to learn about the difference between git and GitHub, and along the way I managed to understand the process of pushing a project from a local repository to an online repository using Git, GitHub, and SSH.&lt;/p&gt;

</description>
      <category>beginners</category>
      <category>git</category>
      <category>github</category>
      <category>tutorial</category>
    </item>
  </channel>
</rss>
