<?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: asma Salah</title>
    <description>The latest articles on DEV Community by asma Salah (@asma_salah_2e2a6926b57a2e).</description>
    <link>https://dev.to/asma_salah_2e2a6926b57a2e</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%2F4071774%2F84e31e95-9c9c-4456-a660-016bbe997796.png</url>
      <title>DEV Community: asma Salah</title>
      <link>https://dev.to/asma_salah_2e2a6926b57a2e</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/asma_salah_2e2a6926b57a2e"/>
    <language>en</language>
    <item>
      <title>Data Modelling, Relationships &amp; Joins</title>
      <dc:creator>asma Salah</dc:creator>
      <pubDate>Sun, 13 Sep 2026 12:58:16 +0000</pubDate>
      <link>https://dev.to/asma_salah_2e2a6926b57a2e/data-modelling-relationships-joins-2jb2</link>
      <guid>https://dev.to/asma_salah_2e2a6926b57a2e/data-modelling-relationships-joins-2jb2</guid>
      <description>&lt;h2&gt;
  
  
  Data Modelling in Power BI
&lt;/h2&gt;

&lt;p&gt;Coming into this, I assumed "data modelling" just meant loading a table into Power BI and building charts on top of it. What it actually means is deciding how your tables relate to each other before you ever touch a visual because that structure is what every DAX calculation, filter, and report performance number depends on downstream. A messy model doesn't just look ugly in the background, it makes calculations slower, filters behave unpredictably, and reports harder to maintain as the business grows.&lt;/p&gt;

&lt;p&gt;There are three common approaches to structuring this:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;flat tables&lt;/li&gt;
&lt;li&gt;star schemas &lt;/li&gt;
&lt;li&gt;snowflake schemas.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Flat table&lt;/strong&gt; is the simplest structure, every piece of data lives in one wide table. A sales record might repeat the customer's name, region, and product category on every single row, because nothing is broken out separately.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Advantages&lt;/strong&gt;: &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;No relationships to configure&lt;/li&gt;
&lt;li&gt;Dead simple to build for tiny datasets&lt;/li&gt;
&lt;li&gt;Easy for a beginner to understand at a glance&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Disadvantages:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Massive redundancy: the same customer name gets typed thousands of times&lt;/li&gt;
&lt;li&gt;Slower performance as row count grows&lt;/li&gt;
&lt;li&gt;DAX measures get harder to write cleanly, since everything is tangled into one table&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;When it's appropriate:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Very small, one-off analyses&lt;/li&gt;
&lt;li&gt;A dataset that will never grow or be reused&lt;/li&gt;
&lt;li&gt;Quick exploratory checks, not a real report&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Performance impact:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Power BI's engine (VertiPaq) compresses data column by column, so repeated values do compress individually&lt;/li&gt;
&lt;li&gt;But forcing one column to carry mixed responsibilities hurts both compression and calculation clarity as the model scales&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;SaleFlat&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Sale ID  Category&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;CustomerName  Quantity&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;ProductName  Revenue&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Region  OrderDate&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;Star Schema&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;A star schema separates data into one central fact table (the measurable events: sales, orders) surrounded by dimension tables (descriptive context: customers, products, dates, locations), each connected directly to the fact table.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Advantages:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Minimal data redundancy&lt;/li&gt;
&lt;li&gt;Fast performance: Power BI's engine is optimized specifically for this shape&lt;/li&gt;
&lt;li&gt;Simple DAX measures&lt;/li&gt;
&lt;li&gt;Easy to understand and extend as the model grows&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Disadvantages:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Requires upfront design effort, you have to decide what's a "fact" vs a "dimension"&lt;/li&gt;
&lt;li&gt;Slightly more setup than just dumping everything into one table&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;When it's appropriate:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Almost always, for any real Power BI report&lt;/li&gt;
&lt;li&gt;It's the industry recommended default schema&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Performance impact:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;This is the exact shape Power BI is built around&lt;/li&gt;
&lt;li&gt;Filters propagate cleanly across one relationship hop&lt;/li&gt;
&lt;li&gt;DAX measures like &lt;code&gt;SUM(FactSales[Revenue])&lt;/code&gt; stay simple and fast&lt;/li&gt;
&lt;/ul&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%2Fek7si7kjimw3m9kxmvd2.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%2Fek7si7kjimw3m9kxmvd2.png" alt="star schema" width="800" height="424"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Snowflake Schema&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;A snowflake schema takes a star schema further by normalizing dimensions, splitting a dimension into multiple related tables. For example, DimProduct might split into DimProduct and DimCategory, so category names aren't repeated across every product row.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Advantages:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Even less data redundancy than a star schema&lt;/li&gt;
&lt;li&gt;Useful when a dimension has a lot of repeated sub-attributes&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Disadvantages:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;More tables means more relationships to manage&lt;/li&gt;
&lt;li&gt;Adds complexity to the model&lt;/li&gt;
&lt;li&gt;Extra joins during query execution can slow performance&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;When it's appropriate:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Large enterprise models where a dimension is genuinely large and repetitive enough that normalizing it saves meaningful storage or performance&lt;/li&gt;
&lt;li&gt;Otherwise it's often unnecessary complexity for a student or small-business project&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Performance impact:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Generally slightly slower than a pure star schema&lt;/li&gt;
&lt;li&gt;Power BI has to traverse an extra relationship hop to get from the fact table to the outer dimension table&lt;/li&gt;
&lt;/ul&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%2Ft78yjm3q0tcq10l3arse.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%2Ft78yjm3q0tcq10l3arse.png" alt="snow flake" width="800" height="392"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Fact Tables and Dimension Tables
&lt;/h3&gt;

&lt;p&gt;Once I understood the star shape, the next question was, how do I actually decide which table is the "fact" and which ones are "dimensions"?. Facts are things that happened, dimensions are things that describe what happened.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Fact Tables&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;A fact table stores the measurable events of the business, the numbers you actually want to add up, average, or count.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What's normally stored in a fact table:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Numeric measures (Quantity, Revenue, Profit)&lt;/li&gt;
&lt;li&gt;Foreign keys linking to each related dimension table&lt;/li&gt;
&lt;li&gt;A timestamp or date key for when the event occurred&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Measures vs. descriptive attributes:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Measures are numbers meant to be aggregated, summed, averaged, counted (e.g. Revenue, UnitsSold)&lt;/li&gt;
&lt;li&gt;Descriptive attributes are labels that explain the measure but aren't themselves calculated (e.g. CustomerName, ProductCategory),these belong in dimension tables, not the fact table&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Grain / granularity:&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Grain means what one single row in the fact table actually represents&lt;br&gt;
&lt;strong&gt;Example:&lt;/strong&gt; if each row is one line item on an order, the grain is "one product per order." If each row is one full order regardless of how many products it contains, the grain is "one order"&lt;br&gt;
Getting the grain wrong early causes major rework later, since every measure and relationship is built assuming a specific row-level meaning&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Examples of fact tables:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;FactSales: one row per sale&lt;/li&gt;
&lt;li&gt;FactOrders: one row per order&lt;/li&gt;
&lt;li&gt;FactTransactions: one row per financial transaction&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Dimension Tables&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;A dimension table stores the descriptive context that explains who, what, where, and when around a fact.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What's normally stored in a dimension table:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Descriptive attributes (names, categories, addresses)&lt;/li&gt;
&lt;li&gt;A unique key that identifies each row (e.g. CustomerID)&lt;/li&gt;
&lt;li&gt;Attributes used for filtering and grouping reports, not for calculation&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Examples of dimension tables:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;DimCustomer: customer name, region, contact info&lt;/li&gt;
&lt;li&gt;DimProduct: product name, category, brand&lt;/li&gt;
&lt;li&gt;DimDate: day, month, quarter, year&lt;/li&gt;
&lt;li&gt;DimLocation: city, country, store branch&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Practical Example: Connecting Fact to Dimensions&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Using the diagram we used earlier, a central FactSales table records every sale, quantity, revenue, and foreign keys pointing to a customer, a product, a date, and a location. It doesn't store the customer's name or the product's category directly; it just stores the keys pointing to those details.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;DimCustomer connects to FactSales so every sale can be traced - back to who bought it&lt;/li&gt;
&lt;li&gt;DimProduct connects to FactSales so every sale can be traced back to what was sold&lt;/li&gt;
&lt;/ul&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%2Frjvuls7iot429j683a57.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%2Frjvuls7iot429j683a57.png" alt=" " width="800" height="424"&gt;&lt;/a&gt;&lt;/p&gt;

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

&lt;p&gt;A relationship is what tells Power BI how two tables connect specifically, which column in one table matches which column in another. Without relationships, every table would sit isolated, and Power BI would have no way to combine a sale in FactSales with the customer who made it in DimCustomer. Relationships are what let a star schema behave like one connected model instead of a pile of unrelated spreadsheets.&lt;/p&gt;

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

&lt;p&gt;&lt;strong&gt;How it works:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;One row on the "1" side can match many rows on the "many" side&lt;/li&gt;
&lt;li&gt;This is the default and most common relationship type in Power BI&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Example:&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;One customer in DimCustomer can appear in many rows of FactSales (a customer can make multiple purchases).&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;When to use it:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Anytime a dimension table describes many events in a fact table, this is the backbone of every star schema&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;When not to use it:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Don't force a 1:* relationship between two tables that don't actually have that structure; it will silently produce wrong aggregations (e.g., inflated totals) if the "1" side isn't actually unique&lt;/li&gt;
&lt;/ul&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%2F6tndql4d2gqrvlz4midr.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%2F6tndql4d2gqrvlz4midr.png" alt=" " width="799" height="332"&gt;&lt;/a&gt;&lt;/p&gt;

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

&lt;p&gt;&lt;strong&gt;How it works:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Exactly one row in Table A matches exactly one row in Table B, with no repeats on either side&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Example:&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;code&gt;Employee&lt;/code&gt; and &lt;code&gt;EmployeeBadge&lt;/code&gt;: Each employee has exactly one badge, and each badge belongs to exactly one employee&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;When to use it:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Rare in practice, mostly used when splitting one table into two for organizational or security reasons (e.g., separating sensitive HR data into its own table)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;When not to use it:&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Don't use 1:1 just because two tables happen to be the same size, confirm the actual relationship is genuinely one-to-one, not coincidentally matching row counts.&lt;/p&gt;

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

&lt;p&gt;&lt;strong&gt;How it works:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Multiple rows in Table A can match multiple rows in Table B, with no single "unique" side.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Example:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Students and Courses: One student takes many courses, and one course has many students enrolled&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;When to use it:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Only when the real-world relationship genuinely has no unique side. Power BI supports this, but it should be a deliberate choice, not a shortcut&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;When not to use it:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Avoid it as a workaround for poor table design; many-to-many relationships make filter behavior harder to predict and can silently produce incorrect totals if not modeled carefully
Keys, Uniqueness, and Referential Integrity&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Primary Keys:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A column that uniquely identifies each row in a table (e.g. CustomerID in DimCustomer)&lt;/li&gt;
&lt;li&gt;Every value in this column must be unique, no duplicates allowed&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Foreign Keys:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A column in another table that references a primary key elsewhere (e.g., CustomerID in FactSales)&lt;/li&gt;
&lt;li&gt;These values are allowed to repeat, since one customer can have many sales&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Why CustomerID is unique in DimCustomer but repeats in FactSales:&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;In DimCustomer, CustomerID is the primary key, each customer appears exactly once&lt;br&gt;
In FactSales, CustomerID is a foreign key, it appears once per sale, so the same customer's ID shows up on every row representing their purchases&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Referential integrity:&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Every foreign key value should have a matching primary key somewhere. A CustomerID in FactSales should always correspond to a real customer in DimCustomer&lt;br&gt;
When this breaks (a foreign key with no match), Power BI treats those rows as "unknown" in visuals, which can silently distort a report if left unnoticed.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Active vs. inactive relationships:&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Power BI only allows one active relationship between two tables at a time; this is the one used automatically in calculations.&lt;br&gt;
Additional relationships between the same two tables can exist but must be marked inactive and are only used when explicitly called with &lt;code&gt;USERELATIONSHIP()&lt;/code&gt; in a DAX formula&lt;br&gt;
&lt;strong&gt;A common example:&lt;/strong&gt; a FactSales table might have both an &lt;code&gt;OrderDate&lt;/code&gt; and a &lt;code&gt;ShipDate&lt;/code&gt;, both relating to ``, but only one can be active by default, so the other stays inactive until a specific measure needs it.&lt;/p&gt;

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

&lt;p&gt;Once tables are related, the next question is: when I click on something in one table, which other tables actually respond to that click? That's what filter direction controls, it determines which way a filter is allowed to "travel" across a relationship.&lt;/p&gt;

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

&lt;p&gt;&lt;strong&gt;How it works:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A filter only flows from the "1" side of a relationship to the "many" side&lt;/li&gt;
&lt;li&gt;This is Power BI's default behavior for relationships in a star schema&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Example:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Selecting a product in DimProduct filters FactSales down to only the sales rows for that product&lt;/li&gt;
&lt;li&gt;But selecting or filtering something in FactSales does not filter DimProduct back, the dimension table stays fully visible&lt;/li&gt;
&lt;/ul&gt;

&lt;h4&gt;
  
  
  Why this is the safe default:
&lt;/h4&gt;

&lt;ul&gt;
&lt;li&gt;It matches how a star schema is meant to work, dimensions describe facts, not the other way around&lt;/li&gt;
&lt;li&gt;It keeps filter behavior predictable and easy to reason about
Bidirectional Filtering&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;How it works:&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The filter flows both ways, selecting something in the fact table can also filter the dimension table, and vice versa&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Example:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Useful in a many-to-many scenario, like filtering a Students table based on a selection in a Courses table, where neither table is a clear "dimension" or "fact"&lt;/li&gt;
&lt;/ul&gt;

&lt;h4&gt;
  
  
  Why it should be used carefully:
&lt;/h4&gt;

&lt;ul&gt;
&lt;li&gt;Ambiguous filter paths: if a model has multiple bidirectional relationships forming loops between tables, Power BI can no longer determine a single clear path for a filter to follow, which can cause errors or force it to pick an unintended path&lt;/li&gt;
&lt;li&gt;Unnecessary model complexity: turning on bidirectional filtering everywhere "just in case" makes the model harder to debug, since every filter interaction now has more possible directions to trace&lt;/li&gt;
&lt;li&gt;Performance cost: filters traveling in both directions across many relationships require more computation than a clean single-direction star schema&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Practical rule of thumb:&lt;/strong&gt; only turn on bidirectional filtering when you have a specific, deliberate reason (like a genuine many-to-many relationship), never as a default setting across your whole model.&lt;/p&gt;

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

&lt;p&gt;A join is how you combine two tables based on a matching column, using Merge Queries in Power Query. Unlike a Power BI relationship, a merge actually pulls columns from one table into another during the data-loading stage, before the model is even built.&lt;/p&gt;

&lt;p&gt;Using my Customers table (6 customers, two of whom, Grace and Mary, have never placed an order) and Orders table (7 orders, one of which references a CustomerID that doesn't exist in Customers), here's what each join type produces:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Inner Join&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Definition:&lt;/strong&gt; keeps only rows where the matching column exists in both tables&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Retained:&lt;/strong&gt; customers 1–4 and their matching orders&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Result:&lt;/strong&gt; 6 rows  Grace, Mary, and the orphaned order (106) are all dropped&lt;/li&gt;
&lt;/ul&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%2Fsib18qjz822smlfqcd3f.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%2Fsib18qjz822smlfqcd3f.png" alt=" " width="799" height="417"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Left Outer Join&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Definition:&lt;/strong&gt; keeps every row from the left (first) table, plus matches from the right table where they exist&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Retained:&lt;/strong&gt; all 6 customers, with order details attached where available, blank/null where not&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Result:&lt;/strong&gt; 8 rows. Grace and Mary appear once each with null order fields&lt;/li&gt;
&lt;/ul&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%2Fbws3kglhk4ihk53lpe8u.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%2Fbws3kglhk4ihk53lpe8u.png" alt=" " width="800" height="423"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Right Outer Join&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Definition:&lt;/strong&gt; keeps every row from the right (second) table, plus matches from the left table where they exist&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Retained:&lt;/strong&gt; all 7 orders, with customer details attached where available&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Result:&lt;/strong&gt; 7 rows; Order 106 appears with null customer fields&lt;/li&gt;
&lt;/ul&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%2Fr01j5x2bif1wvlh1iw0q.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%2Fr01j5x2bif1wvlh1iw0q.png" alt=" " width="799" height="441"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Full Outer Join&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Definition:&lt;/strong&gt; keeps every row from both tables, matched where possible, null where not&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Retained:&lt;/strong&gt; everything, matched rows, Grace, Mary, and Order 106&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Result:&lt;/strong&gt; 9 rows total&lt;/li&gt;
&lt;/ul&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%2Fkrqwndgabtur1u92mq0k.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%2Fkrqwndgabtur1u92mq0k.png" alt=" " width="800" height="409"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Left Anti-Join&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Definition:&lt;/strong&gt; keeps only rows from the left table that have no match in the right table&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Retained:&lt;/strong&gt; customers with zero orders&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Result:&lt;/strong&gt; 2 rows, just Grace and Mary&lt;/li&gt;
&lt;/ul&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%2Fqpux7smpyvq0qqacjoul.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%2Fqpux7smpyvq0qqacjoul.png" alt=" " width="799" height="401"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Right Anti-Join&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Definition:&lt;/strong&gt; keeps only rows from the right table that have no match in the left table&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Retained:&lt;/strong&gt; orders with no valid customer&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Result:&lt;/strong&gt; 1 row, Order 106&lt;/li&gt;
&lt;/ul&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%2F2nzfk9n4aug37ursq6o6.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%2F2nzfk9n4aug37ursq6o6.png" alt=" " width="800" height="412"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Power Query Joins vs. Power BI Relationships
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;A merge and a relationship both "connect" two tables, but they do fundamentally different things, at different stages of the workflow.&lt;/li&gt;
&lt;/ul&gt;

&lt;h4&gt;
  
  
  Does a Power Query merge physically combine data?
&lt;/h4&gt;

&lt;ul&gt;
&lt;li&gt;Yes. A merge creates a genuinely new, combined table, the columns from both tables physically sit together in one result, as I saw when merging Customers and Orders&lt;/li&gt;
&lt;li&gt;This happens during data preparation, before anything is loaded into the model.&lt;/li&gt;
&lt;/ul&gt;

&lt;h4&gt;
  
  
  Does creating a relationship combine the tables?
&lt;/h4&gt;

&lt;ul&gt;
&lt;li&gt;No. A relationship leaves both tables completely separate, &lt;code&gt;DimCustomer&lt;/code&gt; and &lt;code&gt;FactSales&lt;/code&gt; remain two distinct tables in the model&lt;/li&gt;
&lt;li&gt;The relationship just tells Power BI, "These two tables are linked by this key," so it can look up matching rows on demand when a report needs it.&lt;/li&gt;
&lt;/ul&gt;

&lt;h4&gt;
  
  
  At what stage does each happen?
&lt;/h4&gt;

&lt;ul&gt;
&lt;li&gt;Merge in Power Query before the data is loaded into the model&lt;/li&gt;
&lt;li&gt;Relationship to the Data Model, after the data has already been loaded&lt;/li&gt;
&lt;/ul&gt;

&lt;h4&gt;
  
  
  When would you choose a merge instead of a relationship?
&lt;/h4&gt;

&lt;ul&gt;
&lt;li&gt;When you genuinely need one flat, combined table for a specific export or a tool that can't handle relationships&lt;/li&gt;
&lt;li&gt;When a calculation is much simpler to express against one combined table than across a relationship&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Generally:&lt;/strong&gt; rarely, for a proper BI model, most reporting scenarios are better served by relationships&lt;/li&gt;
&lt;/ul&gt;

&lt;h4&gt;
  
  
  How can excessive merging affect the model?
&lt;/h4&gt;

&lt;ul&gt;
&lt;li&gt;Every merge creates a new flattened table, which reintroduces the same redundancy problem a star schema was designed to avoid, the same customer name or product category gets repeated across every row again&lt;/li&gt;
&lt;li&gt;A model full of merged tables starts looking like several disconnected flat tables instead of one clean star schema, making it harder to maintain and slower to query as data grows&lt;/li&gt;
&lt;/ul&gt;

&lt;h4&gt;
  
  
  Why keep fact and dimension tables separate instead of merging them?
&lt;/h4&gt;

&lt;ul&gt;
&lt;li&gt;Smaller table sizes: &lt;code&gt;DimCustomer&lt;/code&gt; stores each customer once, not once per sale&lt;/li&gt;
&lt;li&gt;Cleaner DAX: measures can reference the fact table's numbers and let relationships handle the descriptive context&lt;/li&gt;
&lt;li&gt;Easier maintenance: updating a customer's region means changing one row in DimCustomer, not thousands of rows in a merged mega-table&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Practical example, side by side:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;If I merged Customers into Orders, I'd get one wide table where "Alice Wambui, Nairobi" is repeated on both her orders (101, 104), creating exactly the redundancy problem from flat table explanation.&lt;/li&gt;
&lt;li&gt;If I instead related the two tables, "Alice Wambui, Nairobi" is stored once in Customers, and both her orders in the fact table simply reference her &lt;code&gt;CustomerID&lt;/code&gt;, no repetition, and Power BI resolves the connection only when a report actually needs it&lt;/li&gt;
&lt;/ul&gt;

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

&lt;p&gt;After working through data modelling, relationships, filter direction, and joins hands-on, my recommendation for a typical business intelligence project is a star schema, with single direction one-to-many relationships as the default. Here's the reasoning, broken down by the factors that actually matter in practice:&lt;/p&gt;

&lt;h4&gt;
  
  
  Query and report performance:
&lt;/h4&gt;

&lt;ul&gt;
&lt;li&gt;Power BI's engine (VertiPaq) is specifically optimized around the star schema shape.&lt;/li&gt;
&lt;li&gt;Fewer relationship hops mean filters resolve faster, especially as data volume grows&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;####DAX simplicity:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Measures like SUM(FactSales[Revenue]) stay simple when the fact table holds only numbers and keys&lt;/li&gt;
&lt;li&gt;Snowflake or flat structures force DAX to work around extra joins or repeated columns, adding unnecessary complexity to formulas&lt;/li&gt;
&lt;/ul&gt;

&lt;h4&gt;
  
  
  Model readability:
&lt;/h4&gt;

&lt;ul&gt;
&lt;li&gt;A star schema is visually intuitive in Model View, one glance shows what's a fact and what's a dimension&lt;/li&gt;
&lt;li&gt;A flat table hides this structure entirely; a snowflake schema adds enough branching that it takes longer to read at a glance&lt;/li&gt;
&lt;/ul&gt;

&lt;h4&gt;
  
  
  Scalability:
&lt;/h4&gt;

&lt;ul&gt;
&lt;li&gt;Adding a new dimension (e.g. DimSalesRep) to a star schema is a single new table and one new relationship&lt;/li&gt;
&lt;li&gt;A flat table would require adding and repeating a new column across every existing row instead&lt;/li&gt;
&lt;/ul&gt;

&lt;h4&gt;
  
  
  Data redundancy:
&lt;/h4&gt;

&lt;ul&gt;
&lt;li&gt;A star schema keeps each customer, product, or date stored once, referenced by key, this is the single biggest advantage over a flat table&lt;/li&gt;
&lt;li&gt;Snowflake reduces redundancy even further, but for most business reporting needs, the star schema's redundancy is already low enough that the extra normalization isn't worth the added complexity&lt;/li&gt;
&lt;/ul&gt;

&lt;h4&gt;
  
  
  Maintainability:
&lt;/h4&gt;

&lt;ul&gt;
&lt;li&gt;Updating a customer's region means editing one row in DimCustomer, not hunting through thousands of repeated rows in a flat table&lt;/li&gt;
&lt;li&gt;Fewer tables and simpler relationships (star) are easier to hand off to another analyst than a deeply normalized snowflake model&lt;/li&gt;
&lt;/ul&gt;

&lt;h4&gt;
  
  
  Ease of creating reports:
&lt;/h4&gt;

&lt;ul&gt;
&lt;li&gt;Star schema's clean dimension-to-fact structure means report builders can drag fields into a visual and get correct, predictable aggregations without needing to understand complex relationship paths&lt;/li&gt;
&lt;/ul&gt;

&lt;h4&gt;
  
  
  Filter propagation:
&lt;/h4&gt;

&lt;ul&gt;
&lt;li&gt;Single-direction filtering (dimension to fact) is predictable and matches how most business questions are asked, "show me sales by region," not "show me regions by sales."&lt;/li&gt;
&lt;li&gt;I would only enable bidirectional filtering in a specific, deliberate case, like a genuine many-to-many relationship, never as a blanket default.&lt;/li&gt;
&lt;/ul&gt;

&lt;h4&gt;
  
  
  Model complexity:
&lt;/h4&gt;

&lt;ul&gt;
&lt;li&gt;Star schema hits the sweet spot between "too simple" (flat table, all redundancy) and "too complex" (snowflake, extra hops and relationships) for the vast majority of business reporting needs.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;My default relationship design:&lt;/strong&gt; one-to-many, single-direction, active relationships from each dimension table to the central fact table. I'd only introduce a snowflake normalization if a specific dimension were large and genuinely repetitive enough to justify it, and I'd only use bidirectional filtering or many-to-many relationships when a real business question required it never as a starting default.&lt;/p&gt;

</description>
      <category>analytics</category>
      <category>data</category>
      <category>database</category>
      <category>learning</category>
    </item>
    <item>
      <title>Getting Started with Excel for Data Analytics: From Basics to Data Cleaning</title>
      <dc:creator>asma Salah</dc:creator>
      <pubDate>Mon, 31 Aug 2026 16:26:27 +0000</pubDate>
      <link>https://dev.to/asma_salah_2e2a6926b57a2e/getting-started-with-excel-for-data-analytics-from-basics-to-data-cleaning-12i6</link>
      <guid>https://dev.to/asma_salah_2e2a6926b57a2e/getting-started-with-excel-for-data-analytics-from-basics-to-data-cleaning-12i6</guid>
      <description>&lt;p&gt;When people hear "Excel," they usually picture something simple; rows, columns, maybe a few totals. What I didn't expect going into this week was how much of actual data analytics happens before any analysis even starts. Most of the work is just getting the data into a state where it can be trusted. This week was about learning that groundwork: how to move around a spreadsheet efficiently, how to keep data organized as it grows, and how to clean up the small inconsistencies that quietly break formulas and calculations later.&lt;/p&gt;

&lt;p&gt;To make this practical instead of theoretical, I worked with a sample sales dataset, 40 order records with customer names, products, quantities, prices, dates, regions, and order status. It wasn't a clean dataset on purpose. Names were inconsistently capitalized, some had extra spaces, some prices were stored as text with currency labels attached, a few cells were empty, and two rows were accidental duplicates. In other words, it looked like real data, because real data is almost never clean the first time you see it.&lt;/p&gt;

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

&lt;p&gt;Before cleaning anything, I had to get comfortable with how Excel actually organizes information. A spreadsheet isn't just a table, it's a grid of individually addressable cells, where every cell has a coordinate (like B7), and that coordinate is what formulas and functions actually reference. That single idea is the foundation for everything else: sorting, filtering, formulas, formatting, all of it works because Excel knows exactly where each piece of data lives.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Rows are records, columns are fields. Each row in my dataset was one order; each column was one attribute of that order (Product, Quantity, Price, etc.). Keeping that structure consistent is what makes a spreadsheet usable for analysis instead of just a place to dump numbers.&lt;/li&gt;
&lt;li&gt;Cell references let formulas move with your data. Referencing F2 in a formula, then dragging it down to F40, automatically adjusts to F3, F4, and so on, Excel handles the relative positioning for you.&lt;/li&gt;
&lt;li&gt;Formatting. How a number is formatted (currency, date, plain number) affects how Excel treats it, not just how it looks.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Freezing Panes
&lt;/h2&gt;

&lt;p&gt;With 40 rows of data, it doesn't take long before your headers scroll out of view, and once that happens, it's easy to misread which column is which. Freezing panes solves this by locking the header row and the first column in place while the rest of the sheet scrolls underneath it.&lt;/p&gt;

&lt;p&gt;I did this through &lt;strong&gt;View → Freeze Panes → Freeze Top Row,&lt;/strong&gt; and it immediately made the sheet easier to navigate — I could scroll through all 40 orders and still see exactly which column I was looking at.&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%2Fcbwsy0x9cyi3tduh4xbj.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%2Fcbwsy0x9cyi3tduh4xbj.png" alt=" " width="800" height="445"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Sorting and Filtering
&lt;/h2&gt;

&lt;p&gt;Once the headers were locked in place, the next step was organizing them in a way that reveals patterns..&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Sorting rearranges the entire dataset based on one or more columns. I sorted my sales data by Region first, using &lt;strong&gt;Data → Sort,&lt;/strong&gt; which grouped all orders from the same region together instead of leaving them scattered in the order they were entered. &lt;/li&gt;
&lt;/ul&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%2Fsabhfw7xl6sadjosma73.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%2Fsabhfw7xl6sadjosma73.png" alt=" " width="800" height="425"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Filtering, on the other hand, doesn't rearrange anything,it temporarily hides rows that don't match a condition you set, so you can focus on a subset without losing the rest of the data. I applied a filter using &lt;strong&gt;Ctrl + Shift + L or HOME -&amp;gt; filter &amp;amp; sort&lt;/strong&gt;, which added dropdown arrows to each header. From there, 
I filtered the Status column to show only "Cancelled" orders, which instantly narrowed 40 rows down to a handful, useful if I wanted to investigate cancellations specifically without deleting or reorganizing anything else.&lt;/li&gt;
&lt;/ul&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%2Fnh5ynaflzafb3n5pn25u.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%2Fnh5ynaflzafb3n5pn25u.png" alt=" " width="800" height="416"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Cleaning Text
&lt;/h2&gt;

&lt;p&gt;Scrolling through the Customer Name column also exposed a different problem that the values weren't consistent. Some names were fully uppercase (ALICE WAMBUI), some had random casing (grace Njeri), and a few had extra spaces hiding inside them.&lt;/p&gt;

&lt;p&gt;Two functions fixed this:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;TRIM()&lt;/strong&gt; removes extra spaces, leading, trailing, and repeated spaces between words. Leaving only single spaces between words. =TRIM(B2) cleaned up entries like " Samuel Mwangi".&lt;br&gt;
&lt;strong&gt;PROPER()&lt;/strong&gt; capitalizes the first letter of each word and lowercases the rest, standardizing casing. =PROPER(B2) turned "ALICE wambui" into "Alice Wambui".&lt;/p&gt;

&lt;p&gt;I combined both in a single formula&lt;/p&gt;

&lt;p&gt;&lt;code&gt;=PROPER(TRIM(B2))&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;After applying this down the column, I copied the results and used Paste Special → Values to replace the original messy column with the cleaned, static text, otherwise the formulas would keep referencing the original (still-messy) column.&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%2F543dbc1b5it52dhnw7x9.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%2F543dbc1b5it52dhnw7x9.png" alt=" " width="800" height="426"&gt;&lt;/a&gt;&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%2Fk4j9tue6bxxq0sqjotu9.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%2Fk4j9tue6bxxq0sqjotu9.png" alt=" " width="800" height="430"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Removing Duplicate Rows
&lt;/h2&gt;

&lt;p&gt;Two orders, 1006 and 1013, appeared twice each, with completely identical values across every column. Duplicate rows like this are especially misleading in a sales dataset because they don't just clutter the sheet, they distort any totals or counts calculated from it. &lt;strong&gt;A SUM()&lt;/strong&gt; of the Total column, or a count of orders per region, would both come out inflated if duplicates aren't caught first.&lt;/p&gt;

&lt;p&gt;Excel has a built-in tool for this rather than requiring a manual scan: &lt;strong&gt;Data → Remove Duplicates.&lt;/strong&gt; Selecting the full dataset and running this tool opens a dialog where you choose which columns to check for duplication. &lt;/p&gt;

&lt;h2&gt;
  
  
  Basic Calculations, Formulas, and AutoSum
&lt;/h2&gt;

&lt;p&gt;The Total column had been left empty on purpose, meant to be calculated as Quantity × Unit Price for each order. Rather than typing that out manually 40 times, I used a formula referencing the two relevant cells:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;=E2*F2&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;I then dragged this formula down the entire column, and Excel automatically adjusted the cell references for each row (E3*F3, E4*F4, and so on), calculating every order's total in seconds. &lt;/p&gt;

&lt;p&gt;For a grand total across all orders, I used AutoSum, one of Excel's most basic but genuinely time-saving tools. Selecting the cell directly below the last Total value and pressing Alt + = automatically inserted a SUM() formula covering the entire column, without me needing to type the range manually:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;=SUM(G2:G40)&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;Basic arithmetic operators worked exactly as expected once the data was clean: &lt;strong&gt;+ for addition, - for subtraction, * for multiplication, / for division&lt;/strong&gt;.&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%2Fl89jeibpt27noqmiym5w.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%2Fl89jeibpt27noqmiym5w.png" alt=" " width="800" height="417"&gt;&lt;/a&gt;&lt;/p&gt;

</description>
    </item>
    <item>
      <title>Understanding the Git Workflow: Working Directory, Staging, Commit and Push</title>
      <dc:creator>asma Salah</dc:creator>
      <pubDate>Thu, 27 Aug 2026 09:38:57 +0000</pubDate>
      <link>https://dev.to/asma_salah_2e2a6926b57a2e/understanding-the-git-workflow-working-directory-staging-commit-and-push-1402</link>
      <guid>https://dev.to/asma_salah_2e2a6926b57a2e/understanding-the-git-workflow-working-directory-staging-commit-and-push-1402</guid>
      <description>&lt;h2&gt;
  
  
  Working directory
&lt;/h2&gt;

&lt;p&gt;This is the local project folder; you can create it in the Git terminal by using the following: &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;mkdir folderName&lt;/code&gt; command, for example, &lt;code&gt;mkdir luxDev&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;switching to that folder, we use &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;code&gt;cd folderName&lt;/code&gt; command.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;To create a file in the folder, we use &lt;code&gt;type nul &amp;gt; fileName&lt;/code&gt;&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Then in the event of making changes, we use Git to track the changes.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;git status&lt;/code&gt; command. This command will help us find the untracked changes in files.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Staging
&lt;/h2&gt;

&lt;p&gt;Staging lets you choose what exactly goes into your next commit.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;git add .&lt;/code&gt; is used to stage all changes while, &lt;/li&gt;
&lt;li&gt;
&lt;code&gt;git add fileName&lt;/code&gt; if you want to save a single file or 2 to 3 files&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Commit
&lt;/h2&gt;

&lt;p&gt;This is where git records your staged changes to be permanent point in project history.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;git commit -m "add folder setup and structured the first comment"&lt;/code&gt;&lt;br&gt;
A commit doesn't affect your working directory or your staging area; rather, it takes a snapshot of what's currently staged and locks it into history&lt;/p&gt;

&lt;p&gt;Git also gives you tools to actually see what's happening at each stage, which helped me trust the process instead of just following steps blindly.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;git log&lt;/code&gt; shows the history of commits &lt;/p&gt;

&lt;p&gt;This prints each commit's unique ID, author, date, and message&lt;/p&gt;

&lt;p&gt;&lt;code&gt;git diff&lt;/code&gt; shows exactly what changed in a file, line by line, before you commit it. This is what finally made the working directory.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;git diff&lt;/code&gt;— shows changes in your working directory that are not yet staged&lt;br&gt;
&lt;code&gt;git diff --staged&lt;/code&gt; — shows changes that are staged, waiting to be committed&lt;/p&gt;

&lt;h2&gt;
  
  
  Push
&lt;/h2&gt;

&lt;p&gt;Everything above happens locally, on your own machine. Push is the step where your commit history gets sent to a remote repository (like GitHub), so it's backed up and shareable.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;git push origin main&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;This is the step that made Git "real" for me because before pushing, all my work exists only on my laptop. After pushing, it's on GitHub, visible to anyone I share the repo with, and safe even if my laptop dies tomorrow.&lt;/p&gt;

&lt;h3&gt;
  
  
  Each stage answer a different question
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Working directory:&lt;/strong&gt; What am I currently changing?&lt;br&gt;
&lt;strong&gt;Staging:&lt;/strong&gt; What do I want to include in my next save point?&lt;br&gt;
&lt;strong&gt;Commit:&lt;/strong&gt; What's now permanently recorded in history?&lt;br&gt;
&lt;strong&gt;Push:&lt;/strong&gt; What's now shared with others?&lt;/p&gt;

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