<?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: jim kinyua</title>
    <description>The latest articles on DEV Community by jim kinyua (@jim_kinyua_3f7d191b865bed).</description>
    <link>https://dev.to/jim_kinyua_3f7d191b865bed</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.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F3717512%2F58e17c42-7e6c-4be7-990b-ddf40e98f2fb.jpg</url>
      <title>DEV Community: jim kinyua</title>
      <link>https://dev.to/jim_kinyua_3f7d191b865bed</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/jim_kinyua_3f7d191b865bed"/>
    <language>en</language>
    <item>
      <title>Understanding SQL Joins and Window Functions</title>
      <dc:creator>jim kinyua</dc:creator>
      <pubDate>Mon, 02 Mar 2026 16:00:25 +0000</pubDate>
      <link>https://dev.to/jim_kinyua_3f7d191b865bed/understanding-sql-joins-and-window-functions-30do</link>
      <guid>https://dev.to/jim_kinyua_3f7d191b865bed/understanding-sql-joins-and-window-functions-30do</guid>
      <description>&lt;p&gt;If you've ever stared at a SQL query and wondered what PARTITION BY actually does, this article is for you. I'm going to break down two of the most important SQL concepts &lt;strong&gt;Joins&lt;/strong&gt; and &lt;strong&gt;Window Functions&lt;/strong&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  PART 1: SQL JOINS
&lt;/h2&gt;

&lt;h3&gt;
  
  
  What is a Join?
&lt;/h3&gt;

&lt;p&gt;A Join is how you combine data from two or more tables. Think of it like connecting two spreadsheets using a shared column  like a customer ID that appears in both a Customers table and an Orders table.&lt;/p&gt;

&lt;h3&gt;
  
  
  The Tables We'll Use
&lt;/h3&gt;

&lt;p&gt;Customers:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;id&lt;/th&gt;
&lt;th&gt;name&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;Alice&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;Bob&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;td&gt;Carol&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Orders:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;id&lt;/th&gt;
&lt;th&gt;customer_id&lt;/th&gt;
&lt;th&gt;amount&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;500&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;300&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;200&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Notice that Carol (id = 3) has no orders, and all orders belong to either Alice or Bob. Keep that in mind. it's going to matter a lot when we look at how different JOINs behave.&lt;/p&gt;

&lt;h3&gt;
  
  
  Understanding Left and Right Tables
&lt;/h3&gt;

&lt;p&gt;Before we look at any specific JOIN type, there's one concept you need to lock in first. Every JOIN has a Left table and a Right table, and the difference matters.&lt;/p&gt;

&lt;p&gt;The table written after FROM is the Left table&lt;br&gt;
The table written after JOIN is the Right table&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="n"&gt;customers&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;orders&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;amount&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;customers&lt;/span&gt;          &lt;span class="c1"&gt;-- LEFT table&lt;/span&gt;
&lt;span class="k"&gt;LEFT&lt;/span&gt; &lt;span class="k"&gt;JOIN&lt;/span&gt; &lt;span class="n"&gt;orders&lt;/span&gt;        &lt;span class="c1"&gt;-- RIGHT table&lt;/span&gt;
  &lt;span class="k"&gt;ON&lt;/span&gt; &lt;span class="n"&gt;customers&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;orders&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;customer_id&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This isn't just terminology. SQL uses this position to decide which table's rows get priority when there's no matching data on the other side.&lt;/p&gt;

&lt;h3&gt;
  
  
  INNER JOIN
&lt;/h3&gt;

&lt;p&gt;An INNER JOIN returns rows where there is a match in BOTH tables.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="n"&gt;customers&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;orders&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;amount&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;customers&lt;/span&gt;
&lt;span class="k"&gt;INNER&lt;/span&gt; &lt;span class="k"&gt;JOIN&lt;/span&gt; &lt;span class="n"&gt;orders&lt;/span&gt; &lt;span class="k"&gt;ON&lt;/span&gt; &lt;span class="n"&gt;customers&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;orders&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;customer_id&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Result:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;name&lt;/th&gt;
&lt;th&gt;amount&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Alice&lt;/td&gt;
&lt;td&gt;500&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Alice&lt;/td&gt;
&lt;td&gt;300&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Bob&lt;/td&gt;
&lt;td&gt;200&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Carol is gone. She has no orders, so she doesn't appear. &lt;strong&gt;INNER JOIN&lt;/strong&gt; only shows you where both sides match.&lt;/p&gt;

&lt;h3&gt;
  
  
  LEFT JOIN
&lt;/h3&gt;

&lt;p&gt;A LEFT JOIN returns ALL rows from the left table, and matches from the right. Where there's no match, you get NULL.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="n"&gt;customers&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;orders&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;amount&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;customers&lt;/span&gt;
&lt;span class="k"&gt;LEFT&lt;/span&gt; &lt;span class="k"&gt;JOIN&lt;/span&gt; &lt;span class="n"&gt;orders&lt;/span&gt; &lt;span class="k"&gt;ON&lt;/span&gt; &lt;span class="n"&gt;customers&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;orders&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;customer_id&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Result:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;name&lt;/th&gt;
&lt;th&gt;amount&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Alice&lt;/td&gt;
&lt;td&gt;500&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Alice&lt;/td&gt;
&lt;td&gt;300&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Bob&lt;/td&gt;
&lt;td&gt;200&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Carol&lt;/td&gt;
&lt;td&gt;NULL&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Carol is back. She has no orders, so her amount is NULL. Use LEFT JOIN when you don't want to lose rows from your main table.&lt;/p&gt;

&lt;h3&gt;
  
  
  RIGHT JOIN
&lt;/h3&gt;

&lt;p&gt;The opposite of LEFT JOIN. All rows from the right table are kept, and matches come from the left. Less common, but useful in certain situations.&lt;/p&gt;

&lt;h3&gt;
  
  
  FULL OUTER JOIN
&lt;/h3&gt;

&lt;p&gt;Returns all rows from both tables, with NULLs where there's no match on either side.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="n"&gt;customers&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;orders&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;amount&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;customers&lt;/span&gt;
&lt;span class="k"&gt;FULL&lt;/span&gt; &lt;span class="k"&gt;OUTER&lt;/span&gt; &lt;span class="k"&gt;JOIN&lt;/span&gt; &lt;span class="n"&gt;orders&lt;/span&gt; &lt;span class="k"&gt;ON&lt;/span&gt; &lt;span class="n"&gt;customers&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;orders&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;customer_id&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Use this when you want to see unmatched records from both sides.&lt;/p&gt;

&lt;h3&gt;
  
  
  A Simple Way to Remember Joins
&lt;/h3&gt;

&lt;p&gt;INNER  = Only the overlap between the two tables&lt;br&gt;
LEFT   = All of left + whatever matches on the right&lt;br&gt;
RIGHT  = All of right + whatever matches on the left&lt;br&gt;
FULL   = Everything from both, matched or not&lt;/p&gt;


&lt;h3&gt;
  
  
  Key Takeaways for Joins
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;Joins combine tables using a shared column&lt;/li&gt;
&lt;li&gt;INNER JOIN is the most common — only matched rows&lt;/li&gt;
&lt;li&gt;LEFT JOIN is your friend when you don't want to lose rows from your primary table&lt;/li&gt;
&lt;li&gt;Always think: "What happens to rows with no match?" this is what determines which join to use&lt;/li&gt;
&lt;/ol&gt;
&lt;h2&gt;
  
  
  PART 2: WINDOW FUNCTIONS
&lt;/h2&gt;
&lt;h3&gt;
  
  
  What is a Window Function?
&lt;/h3&gt;

&lt;p&gt;A window function performs a calculation across a set of rows related to the current row  without collapsing them like GROUP BY does.&lt;/p&gt;

&lt;p&gt;This is the key difference:&lt;/p&gt;

&lt;p&gt;GROUP BY: Collapses multiple rows into one result per group&lt;br&gt;
Window Function: Keeps all rows, and adds a calculated column to each one&lt;/p&gt;

&lt;p&gt;Think of it like this. for each row, you "open a window" into the data, do a calculation across that window, and write the result back into that row without losing any rows.&lt;/p&gt;
&lt;h3&gt;
  
  
  The Syntax
&lt;/h3&gt;


&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;function&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="n"&gt;OVER&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;PARTITION&lt;/span&gt; &lt;span class="k"&gt;BY&lt;/span&gt; &lt;span class="k"&gt;column&lt;/span&gt; &lt;span class="k"&gt;ORDER&lt;/span&gt; &lt;span class="k"&gt;BY&lt;/span&gt; &lt;span class="k"&gt;column&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;OVER is what makes it a window function. Without OVER, it's just a regular function.&lt;/p&gt;
&lt;h3&gt;
  
  
  The Table We'll Use
&lt;/h3&gt;

&lt;p&gt;Sales:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;employee&lt;/th&gt;
&lt;th&gt;dept&lt;/th&gt;
&lt;th&gt;sales&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Alice&lt;/td&gt;
&lt;td&gt;IT&lt;/td&gt;
&lt;td&gt;500&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Bob&lt;/td&gt;
&lt;td&gt;IT&lt;/td&gt;
&lt;td&gt;300&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Carol&lt;/td&gt;
&lt;td&gt;HR&lt;/td&gt;
&lt;td&gt;400&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Dave&lt;/td&gt;
&lt;td&gt;HR&lt;/td&gt;
&lt;td&gt;600&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;
&lt;h3&gt;
  
  
  ROW_NUMBER() — Rank Rows Within a Group
&lt;/h3&gt;


&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="n"&gt;employee&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;dept&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;sales&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;ROW_NUMBER&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="n"&gt;OVER&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;PARTITION&lt;/span&gt; &lt;span class="k"&gt;BY&lt;/span&gt; &lt;span class="n"&gt;dept&lt;/span&gt; &lt;span class="k"&gt;ORDER&lt;/span&gt; &lt;span class="k"&gt;BY&lt;/span&gt; &lt;span class="n"&gt;sales&lt;/span&gt; &lt;span class="k"&gt;DESC&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;rank&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;sales&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Result:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;employee&lt;/th&gt;
&lt;th&gt;dept&lt;/th&gt;
&lt;th&gt;sales&lt;/th&gt;
&lt;th&gt;rank&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Alice&lt;/td&gt;
&lt;td&gt;IT&lt;/td&gt;
&lt;td&gt;500&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Bob&lt;/td&gt;
&lt;td&gt;IT&lt;/td&gt;
&lt;td&gt;300&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Dave&lt;/td&gt;
&lt;td&gt;HR&lt;/td&gt;
&lt;td&gt;600&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Carol&lt;/td&gt;
&lt;td&gt;HR&lt;/td&gt;
&lt;td&gt;400&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;*&lt;em&gt;What happened here?&lt;br&gt;
*&lt;/em&gt;&lt;br&gt;
PARTITION BY dept splits the data into two groups: IT and HR.&lt;br&gt;
ORDER BY sales DESC sorts each group from highest to lowest sales.&lt;br&gt;
ROW_NUMBER() assigns 1, 2, 3... to each row within that group.&lt;/p&gt;

&lt;p&gt;Both Alice and Dave get rank 1 — because the ranking restarted for each department. That's the power of PARTITION BY.&lt;/p&gt;
&lt;h3&gt;
  
  
  RANK() vs DENSE_RANK() vs ROW_NUMBER()
&lt;/h3&gt;

&lt;p&gt;These three are easy to confuse. Here's the difference when two people tie:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="n"&gt;employee&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;sales&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;ROW_NUMBER&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="n"&gt;OVER&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;ORDER&lt;/span&gt; &lt;span class="k"&gt;BY&lt;/span&gt; &lt;span class="n"&gt;sales&lt;/span&gt; &lt;span class="k"&gt;DESC&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;row_num&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;RANK&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;       &lt;span class="n"&gt;OVER&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;ORDER&lt;/span&gt; &lt;span class="k"&gt;BY&lt;/span&gt; &lt;span class="n"&gt;sales&lt;/span&gt; &lt;span class="k"&gt;DESC&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;rnk&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;DENSE_RANK&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="n"&gt;OVER&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;ORDER&lt;/span&gt; &lt;span class="k"&gt;BY&lt;/span&gt; &lt;span class="n"&gt;sales&lt;/span&gt; &lt;span class="k"&gt;DESC&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;dense_rnk&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;sales&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;employee&lt;/th&gt;
&lt;th&gt;sales&lt;/th&gt;
&lt;th&gt;row_num&lt;/th&gt;
&lt;th&gt;rnk&lt;/th&gt;
&lt;th&gt;dense_rnk&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Dave&lt;/td&gt;
&lt;td&gt;600&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Alice&lt;/td&gt;
&lt;td&gt;500&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Carol&lt;/td&gt;
&lt;td&gt;500&lt;/td&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Bob&lt;/td&gt;
&lt;td&gt;300&lt;/td&gt;
&lt;td&gt;4&lt;/td&gt;
&lt;td&gt;4&lt;/td&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;ROW_NUMBER: Always unique. No ties. Just counts 1, 2, 3, 4.&lt;br&gt;
RANK: Ties get the same number, then skips. Alice and Carol both get 2, next is 4.&lt;br&gt;
DENSE_RANK: Ties get the same number, but does NOT skip. Next after 2 is 3.&lt;/p&gt;
&lt;h3&gt;
  
  
  SUM() OVER()
&lt;/h3&gt;


&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="n"&gt;employee&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;dept&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;sales&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="k"&gt;SUM&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;sales&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;OVER&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;PARTITION&lt;/span&gt; &lt;span class="k"&gt;BY&lt;/span&gt; &lt;span class="n"&gt;dept&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;dept_total&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;sales&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Result:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;employee&lt;/th&gt;
&lt;th&gt;dept&lt;/th&gt;
&lt;th&gt;sales&lt;/th&gt;
&lt;th&gt;dept_total&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Alice&lt;/td&gt;
&lt;td&gt;IT&lt;/td&gt;
&lt;td&gt;500&lt;/td&gt;
&lt;td&gt;800&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Bob&lt;/td&gt;
&lt;td&gt;IT&lt;/td&gt;
&lt;td&gt;300&lt;/td&gt;
&lt;td&gt;800&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Dave&lt;/td&gt;
&lt;td&gt;HR&lt;/td&gt;
&lt;td&gt;600&lt;/td&gt;
&lt;td&gt;1000&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Carol&lt;/td&gt;
&lt;td&gt;HR&lt;/td&gt;
&lt;td&gt;400&lt;/td&gt;
&lt;td&gt;1000&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Every row still exists. But now each row also shows the total for their department. With GROUP BY, you'd lose the individual rows. With SUM() OVER(), you keep them all.&lt;/p&gt;


&lt;h3&gt;
  
  
  LAG() and LEAD()
&lt;/h3&gt;

&lt;p&gt;LAG() lets you look at the value from the previous row. LEAD() looks ahead to the next row. Perfect for comparing month-over-month changes.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="n"&gt;employee&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;sales&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;LAG&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;sales&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;  &lt;span class="n"&gt;OVER&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;ORDER&lt;/span&gt; &lt;span class="k"&gt;BY&lt;/span&gt; &lt;span class="n"&gt;sales&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;previous_sales&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;LEAD&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;sales&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;OVER&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;ORDER&lt;/span&gt; &lt;span class="k"&gt;BY&lt;/span&gt; &lt;span class="n"&gt;sales&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;next_sales&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;sales&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Result:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;employee&lt;/th&gt;
&lt;th&gt;sales&lt;/th&gt;
&lt;th&gt;previous_sales&lt;/th&gt;
&lt;th&gt;next_sales&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Bob&lt;/td&gt;
&lt;td&gt;300&lt;/td&gt;
&lt;td&gt;NULL&lt;/td&gt;
&lt;td&gt;400&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Carol&lt;/td&gt;
&lt;td&gt;400&lt;/td&gt;
&lt;td&gt;300&lt;/td&gt;
&lt;td&gt;500&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Alice&lt;/td&gt;
&lt;td&gt;500&lt;/td&gt;
&lt;td&gt;400&lt;/td&gt;
&lt;td&gt;600&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Dave&lt;/td&gt;
&lt;td&gt;600&lt;/td&gt;
&lt;td&gt;500&lt;/td&gt;
&lt;td&gt;NULL&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The first row has no previous row, so it's NULL. The last row has no next row, so that's NULL too.&lt;/p&gt;

&lt;h3&gt;
  
  
  ROWS BETWEEN
&lt;/h3&gt;

&lt;p&gt;This is more advanced, but incredibly useful for moving averages. It is mostly used to control the Size of Your Window&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="n"&gt;employee&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;sales&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="k"&gt;AVG&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;sales&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;OVER&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="k"&gt;ORDER&lt;/span&gt; &lt;span class="k"&gt;BY&lt;/span&gt; &lt;span class="n"&gt;sales&lt;/span&gt;
    &lt;span class="k"&gt;ROWS&lt;/span&gt; &lt;span class="k"&gt;BETWEEN&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="k"&gt;PRECEDING&lt;/span&gt; &lt;span class="k"&gt;AND&lt;/span&gt; &lt;span class="k"&gt;CURRENT&lt;/span&gt; &lt;span class="k"&gt;ROW&lt;/span&gt;
  &lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;moving_avg&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;sales&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This calculates the average of the current row and the one before it. Change the numbers to control how many rows back or forward your window looks.&lt;/p&gt;

&lt;h3&gt;
  
  
  Key Takeaways for Window Functions
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;Window functions do NOT collapse rows — this is the biggest difference from GROUP BY&lt;/li&gt;
&lt;li&gt;OVER() is what makes it a window function&lt;/li&gt;
&lt;li&gt;PARTITION BY is like GROUP BY but keeps all rows&lt;/li&gt;
&lt;li&gt;ORDER BY inside OVER() controls the order within the win&lt;/li&gt;
&lt;/ol&gt;

</description>
      <category>beginners</category>
      <category>database</category>
      <category>sql</category>
      <category>tutorial</category>
    </item>
    <item>
      <title>How to Turn Messy Data, DAX Headaches, and Ugly Dashboards into Decisions Using Power BI</title>
      <dc:creator>jim kinyua</dc:creator>
      <pubDate>Mon, 09 Feb 2026 15:20:17 +0000</pubDate>
      <link>https://dev.to/jim_kinyua_3f7d191b865bed/how-to-turn-messy-data-dax-headaches-and-ugly-dashboards-into-decisions-using-power-bi-1fgd</link>
      <guid>https://dev.to/jim_kinyua_3f7d191b865bed/how-to-turn-messy-data-dax-headaches-and-ugly-dashboards-into-decisions-using-power-bi-1fgd</guid>
      <description>&lt;p&gt;Let’s be honest, no dataset has ever arrived on an analyst's desk completely clean. Not once. In the real world, data usually shows up in a chaotic state. Dates saved as text, revenue strings mixed with currency symbols, twelve different spellings for the same county, blanks that should be zeros, and zeros that are actually missing values.&lt;/p&gt;

&lt;p&gt;Inevitably, this is followed by a stakeholder asking, "Can you build a dashboard by Friday?" The answer is usually "sure," but the gap between receiving that raw data and delivering a "wow" dashboard is where the actual work happens. It is a journey that moves from invisible data engineering to strategic business storytelling.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The Invisible Foundation: Power Query&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The first reality of analytics is that building the dashboard is often the easiest part; dragging charts onto a canvas takes minutes. The real labor lies in ensuring those charts tell the truth, and that work begins in Power Query. This is the "cleaning room" where we fix data types (because revenue should never be text), standardize categories, remove duplicates, and handle null values properly. It is also where we create derived fields, such as "Age Group" or "Price Band," to make analysis easier later on.&lt;/p&gt;

&lt;p&gt;If you skip this step, you will inevitably try to fix data quality issues using DAX measures. This is a mistake. DAX is a calculation engine, not a cleanup tool, and it will punish you with slow performance and overly complex formulas if your data isn't prepared correctly.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The Backbone: Data Modeling&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Most beginners make the mistake of dumping all their data into one massive, flat table. While this might work for simple spreadsheets, Power BI’s engine is optimized for a Star Schema. This means separating your data into a Fact table (containing transactions, visits, or sales numbers) and Dimension tables (containing descriptive context like dates, counties, products, or departments).&lt;/p&gt;

&lt;p&gt;When your relationships are modeled correctly in a star schema, filters flow logically, totals don't double-count, and performance improves significantly. A bad model forces you to write complex DAX to work around the structure; a good model allows for elegant, simple DAX.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The Logic: Context Over Formulas&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Once the model is solid, we move to DAX. On the surface, it looks simple—a formula like &lt;code&gt;Total Revenue = SUM(Sales[Revenue])&lt;/code&gt; seems straightforward. However, the real power of DAX is context. That single measure will return different results based on slicers, filters, relationships, and the visual it is placed in.&lt;/p&gt;

&lt;p&gt;For example, a measure like &lt;code&gt;Revenue per Visit = DIVIDE([Total Revenue], [Total Visits])&lt;/code&gt; does more than just report a number; it measures performance. Understanding "filter context" how the user's interaction with the report changes the calculation on the fly—is the moment DAX stops being frustrating and starts making sense.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The Art of Visualization: How to Choose the Right Chart&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The visual layer is where your data meets the user’s eye. The wrong visual can obscure the truth, while the right one illuminates it. To choose the best visual, you must first identify the question you are trying to answer.&lt;/p&gt;

&lt;p&gt;Here is a framework for selecting the right visual.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;1. Comparison&lt;/strong&gt;&lt;br&gt;
If you want to compare values across categories (e.g., Sales by Department), use a Bar Chart. If you are comparing values over time (e.g., Sales by Month), use a Line Chart or Area Chart to show the trend&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;2. Correlation&lt;/strong&gt;&lt;br&gt;
If you need to see if two variables are related for instance, "Does higher patient visits always mean higher revenue?" you should use a Scatter Plot. With "Total Visits" on the X-axis and "Total Revenue" on the Y-axis, you can instantly see positive correlations, outliers, or underperforming counties.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;3. Composition&lt;/strong&gt;&lt;br&gt;
If you need to show how parts make up a whole (e.g., Market Share), use a &lt;strong&gt;Donut Chart&lt;/strong&gt; or &lt;strong&gt;Treemap&lt;/strong&gt;. Use these sparingly; too many slices make them unreadable.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;4. KPIs&lt;/strong&gt;&lt;br&gt;
If you just need to show a single, critical number (e.g., Total Year-to-Date Revenue), use a Card or a KPI Visual that shows the number alongside a trend indicator.&lt;/p&gt;

&lt;p&gt;Dashboards are storytelling tools. Each visual should answer exactly one question. If a chart requires a paragraph of explanation, it is likely the wrong chart.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;From Insight to Action&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The ultimate goal of this entire process is not to build a dashboard that looks "clean," but to drive action. We transform messy data into business strategy by highlighting anomalies and trends that require intervention.&lt;/p&gt;

&lt;p&gt;For example, seeing high visits but low revenue might indicate a pricing issue. Noticing high medication usage in a specific age group drives inventory planning. A declining trend over time signals operational risk. A good analyst reports the numbers; a great analyst explains what they mean and what to do next.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Final Thoughts&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Power BI is not just a dashboard tool; it is a thinking tool. The workflow—moving from Messy Data to Clean Transformations, building a Strong Model, writing Smart DAX, designing Clear Visuals, and finally driving Business Action—is the true craft of analytics.&lt;/p&gt;

&lt;p&gt;It isn’t about knowing every DAX function by heart. It’s about knowing when to clean, how to simplify logic, and how to explain insights to stakeholders. That is what makes a good analyst, and honestly, that is what makes analytics fun.&lt;/p&gt;

</description>
      <category>analytics</category>
      <category>dataengineering</category>
      <category>microsoft</category>
      <category>tutorial</category>
    </item>
    <item>
      <title>Schemas and Data Modelling in Power BI</title>
      <dc:creator>jim kinyua</dc:creator>
      <pubDate>Mon, 02 Feb 2026 12:34:24 +0000</pubDate>
      <link>https://dev.to/jim_kinyua_3f7d191b865bed/schemas-and-data-modelling-in-power-bi-34k5</link>
      <guid>https://dev.to/jim_kinyua_3f7d191b865bed/schemas-and-data-modelling-in-power-bi-34k5</guid>
      <description>&lt;p&gt;Everyone loves building flashy visuals and writing clever DAX measures in Power BI, and those things definitely matter. But the truth is, the single biggest factor in whether your report performs well, scales nicely, and gives trustworthy results is data modelling.&lt;/p&gt;

&lt;p&gt;Get the model right, and everything else becomes easier: calculations run faster, measures are simpler to write, filters behave as expected, and totals add up correctly. Get it wrong, and you’ll spend endless hours fighting performance issues, chasing phantom duplicates, or explaining why the numbers don’t match what the business expects.&lt;/p&gt;

&lt;p&gt;Data modelling is basically about answering three core questions:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Where do the numbers live? (sales amounts, quantities, revenue)&lt;/li&gt;
&lt;li&gt;Where do the descriptive attributes live? (product names, customer details, dates etc)&lt;/li&gt;
&lt;li&gt;How do all these tables connect?&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;strong&gt;Fact Tables vs. Dimension Tables&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Fact tables&lt;/strong&gt;&lt;br&gt;
They hold the measurable events. These are the things that happened in the business. Think sales transactions, orders, inventory movements, clicks, or support tickets. They’re usually full of numeric values (facts) and foreign keys that reference other tables.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Dimension tables&lt;/strong&gt;&lt;br&gt;
They provide the "who, what, where, when, why" context. They contain descriptive fields and are typically much smaller than fact tables.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The Star Schema&lt;/strong&gt;&lt;br&gt;
In a star schema, you have the following&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;One central fact table
&lt;/li&gt;
&lt;li&gt;Several dimension tables connected directly to it (usually via single-direction relationships from dimension to fact)&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Hence the name Star.&lt;br&gt;
It's important to note that the star schema is the gold standard for Power BI (and most modern BI tools). It’s simple, fast, and plays perfectly to how Power BI’s engine (VertiPaq) works.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The Snowflake Schema&lt;/strong&gt;&lt;br&gt;
A snowflake schema is basically a normalized star schema. Instead of keeping everything in one dimension table, you split dimensions into multiple related tables (e.g., Product → Category → Subcategory → Department).&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Comparison Between Star and  Snowflake&lt;/strong&gt;&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Feature&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;Complexity&lt;/td&gt;
&lt;td&gt;Simple and intuitive&lt;/td&gt;
&lt;td&gt;More complex&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Query performance&lt;/td&gt;
&lt;td&gt;Faster (fewer joins)&lt;/td&gt;
&lt;td&gt;Slightly slower&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;DAX development&lt;/td&gt;
&lt;td&gt;Much easier&lt;/td&gt;
&lt;td&gt;More complicated&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Bottom line: Use a star schema in Power BI unless you have a massive warehouse where normalization savings outweigh speed.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Why Good Data Modelling Actually Matters&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;A good model is not a “nice to have” but will affect every aspect of your reporting experience. Performance comes first. The column-store engine (VertiPaq) in Power BI is highly optimized for star schema models. This means there are fewer tables to join, and the relationships are simpler, so queries run much faster — especially as users begin to slice and dice the data. A flat table or a “snowflake” mess will slow things down considerably&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Accuracy is a must&lt;/strong&gt;. When relationships are defined correctly (with the right cardinality and cross-filtering), filters work as expected, totals aren’t double-counted, and grand totals match what’s expected at lower levels. Get this wrong, and you’ll get inconsistent or wrong numbers — the quickest way to undermine trust in your reports.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Simpler DAX formulas are a massive benefit&lt;/strong&gt;. In a properly modeled star schema, most calculations can be simple aggregations (SUM, COUNT, AVERAGE) with CALCULATE filters as needed. No more complex workarounds, heavy use of iterators, or “treat the table like an Excel sheet.” Debugging is simpler too — usually, problems are visible in seconds, not hours.&lt;/p&gt;

&lt;p&gt;Finally, &lt;strong&gt;maintainability and scalability&lt;/strong&gt;. A model that’s clean and logical is easy for others (or for you, later) to understand. It scales well as you add more data or subject areas, and you can often reuse the same model in multiple reports or even share it in a Power BI dataset for the whole team.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;In short&lt;/strong&gt;: Take the time to model your data properly. The difference between a report that feels snappy and reliable and one that’s always fighting you is huge. The visuals and DAX formulas are great, but they’re only as good as the foundation they’re built on.&lt;/p&gt;

</description>
      <category>analytics</category>
      <category>architecture</category>
      <category>data</category>
      <category>microsoft</category>
    </item>
    <item>
      <title>Git for Beginners: How to Push &amp; Pull Code, Track Changes, and Understand Version Control</title>
      <dc:creator>jim kinyua</dc:creator>
      <pubDate>Sun, 18 Jan 2026 07:38:53 +0000</pubDate>
      <link>https://dev.to/jim_kinyua_3f7d191b865bed/git-for-beginners-how-to-push-pull-code-track-changes-and-understand-version-control-4443</link>
      <guid>https://dev.to/jim_kinyua_3f7d191b865bed/git-for-beginners-how-to-push-pull-code-track-changes-and-understand-version-control-4443</guid>
      <description>&lt;h1&gt;
  
  
  Git Basics for Beginners: Push, Pull, and Track Code Changes
&lt;/h1&gt;

&lt;p&gt;Git is a version control tool that helps developers track changes in their code and collaborate safely. Instead of creating multiple versions of the same file, Git records what changed, when it changed, and who made the change. This makes it easy to work in teams and recover from mistakes.&lt;/p&gt;

&lt;p&gt;Git works in a project folder called a &lt;strong&gt;repository&lt;/strong&gt;. You work locally on your computer, then sync your changes to a remote repository such as GitHub.&lt;/p&gt;

&lt;h2&gt;
  
  
  Creating a Git Repository
&lt;/h2&gt;

&lt;p&gt;Start tracking a new project:&lt;br&gt;
git init&lt;/p&gt;

&lt;p&gt;Copy an existing project:&lt;br&gt;
git clone &lt;a href="https://github.com/username/project.git" rel="noopener noreferrer"&gt;https://github.com/username/project.git&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Tracking Changes
&lt;/h2&gt;

&lt;p&gt;Check the current state of your project:&lt;br&gt;
git status&lt;/p&gt;

&lt;p&gt;Stage a specific file:&lt;br&gt;
git add filename.js&lt;/p&gt;

&lt;p&gt;Stage all changes:&lt;br&gt;
git add .&lt;/p&gt;

&lt;p&gt;Save a checkpoint:&lt;br&gt;
git commit -m "Add login validation"&lt;/p&gt;

&lt;h2&gt;
  
  
  Pushing and Pulling Code
&lt;/h2&gt;

&lt;p&gt;Send your commits to the remote repository:&lt;br&gt;
git push&lt;/p&gt;

&lt;p&gt;First-time push:&lt;br&gt;
git push -u origin main&lt;/p&gt;

&lt;p&gt;Get the latest updates from the remote repository:&lt;br&gt;
git pull&lt;/p&gt;

&lt;p&gt;Always pull before starting work to avoid conflicts.&lt;/p&gt;

&lt;h2&gt;
  
  
  A Typical Daily Git Workflow
&lt;/h2&gt;

&lt;p&gt;git pull  &lt;/p&gt;

&lt;h1&gt;
  
  
  write code
&lt;/h1&gt;

&lt;p&gt;git status&lt;br&gt;&lt;br&gt;
git add .&lt;br&gt;&lt;br&gt;
git commit -m "Fix payment validation bug"&lt;br&gt;&lt;br&gt;
git push&lt;/p&gt;

&lt;h2&gt;
  
  
  Viewing History and Changes
&lt;/h2&gt;

&lt;p&gt;View commit history:&lt;br&gt;
git log&lt;/p&gt;

&lt;p&gt;See what changed in files:&lt;br&gt;
git diff&lt;/p&gt;

&lt;h2&gt;
  
  
  Common Beginner Mistakes
&lt;/h2&gt;

&lt;p&gt;Forgetting to pull before working, writing unclear commit messages, committing broken code, or being afraid to make mistakes. Git is designed to recover from errors safely.&lt;/p&gt;

&lt;h2&gt;
  
  
  Final Thoughts
&lt;/h2&gt;

&lt;p&gt;Git is simply about tracking changes, saving checkpoints, and syncing work with others. Learn these core commands first: git status, git add, git commit, git push, and git pull. Everything else builds on them.&lt;/p&gt;

</description>
      <category>webdev</category>
    </item>
  </channel>
</rss>
