<?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: Mercy Siele</title>
    <description>The latest articles on DEV Community by Mercy Siele (@siele).</description>
    <link>https://dev.to/siele</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%2F4071740%2Fa21e7bc3-1edc-40ee-bd2b-177cbcacf616.png</url>
      <title>DEV Community: Mercy Siele</title>
      <link>https://dev.to/siele</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/siele"/>
    <language>en</language>
    <item>
      <title>Mastering Advanced PostgreSQL: A Comprehensive Guide to Hospital Data Intelligence</title>
      <dc:creator>Mercy Siele</dc:creator>
      <pubDate>Sun, 18 Oct 2026 15:11:19 +0000</pubDate>
      <link>https://dev.to/siele/mastering-advanced-postgresql-a-comprehensive-guide-to-hospital-data-intelligence-24o0</link>
      <guid>https://dev.to/siele/mastering-advanced-postgresql-a-comprehensive-guide-to-hospital-data-intelligence-24o0</guid>
      <description>&lt;h2&gt;
  
  
  Introduction
&lt;/h2&gt;

&lt;p&gt;Modern healthcare systems generate vast amounts of relational data daily, from patient admissions and doctor shift schedules to treatment costs and departmental expenses. Transforming this raw data into meaningful clinical and financial insights requires more than basic SQL queries. Database administrators and data analysts must leverage advanced querying techniques to uncover patterns, improve operational efficiency, and support data-driven decision-making.&lt;/p&gt;

&lt;p&gt;This article explores four essential PostgreSQL concepts: &lt;strong&gt;Window Functions, Joins, Common Table Expressions (CTEs), and Subqueries&lt;/strong&gt;. Using a realistic Hospital Management Dataset, each concept is demonstrated through practical SQL examples that illustrate how to analyze healthcare data and extract actionable insights.Each concept is explained using practical scenarios based on the hospital management database, which contains the following tables: doctors, department, diagnosis, wards, patients, procedures, and visits.&lt;/p&gt;

&lt;h2&gt;
  
  
  SQL Window Functions
&lt;/h2&gt;

&lt;p&gt;SQL window functions perform calculations across a set of table rows that are related to the current row, without collapsing the result set into a single summary row. Unlike standard GROUP BY operations which merge rows to give you a single total, window functions preserve the identity of each individual row while appending the calculated aggregate or analytical value right next to it.&lt;/p&gt;

&lt;h3&gt;
  
  
  Understanding &lt;code&gt;OVER()&lt;/code&gt;, &lt;code&gt;PARTITION BY&lt;/code&gt;, &lt;code&gt;ORDER BY&lt;/code&gt;, and window frames
&lt;/h3&gt;

&lt;p&gt;These elements define how a window function evaluates rows.&lt;/p&gt;

&lt;h4&gt;
  
  
  The &lt;code&gt;OVER()&lt;/code&gt; Clause
&lt;/h4&gt;

&lt;p&gt;The &lt;code&gt;OVER()&lt;/code&gt; clause specifies the window of rows used by a window function. Depending on the function and query, it can contain a partition, an ordering, and a window frame.&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%2Fwiujqfbgaf8gr4oo7g6z.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%2Fwiujqfbgaf8gr4oo7g6z.png" alt=" " width="729" height="147"&gt;&lt;/a&gt;&lt;br&gt;
Result:&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%2Fpwdpggb0rp222hgnfo7s.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%2Fpwdpggb0rp222hgnfo7s.png" alt=" " width="784" height="268"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What it does&lt;/strong&gt;: Calculates the average bill across all visits and displays that same average alongside every visit.&lt;br&gt;
&lt;strong&gt;Why it is useful&lt;/strong&gt;: Analysts can compare individual bills with the overall hospital average without losing the individual visit records.&lt;/p&gt;

&lt;h4&gt;
  
  
  The &lt;code&gt;PARTITION BY&lt;/code&gt; Clause
&lt;/h4&gt;

&lt;p&gt;&lt;code&gt;PARTITION BY&lt;/code&gt; divides rows into groups for a window calculation without collapsing them into summary rows.&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%2Fmtt5418am6vjcjt5ygt1.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%2Fmtt5418am6vjcjt5ygt1.png" alt=" " width="514" height="211"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Result:&lt;br&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%2Fm2oz3ku295cbsznixoom.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%2Fm2oz3ku295cbsznixoom.png" alt=" " width="800" height="214"&gt;&lt;/a&gt;&lt;br&gt;
&lt;strong&gt;What it does:&lt;/strong&gt; Calculates the average bill separately for each patient.&lt;br&gt;
&lt;strong&gt;Result&lt;/strong&gt;: Each visit remains in the output, with an additional column showing that patient's average bill across their recorded visits.&lt;br&gt;
If you had used a standard GROUP BY patient_id, PostgreSQL would have collapsed your rows down to just one row per patient, losing the individual visit_id and total_bill_kes details.&lt;/p&gt;

&lt;h4&gt;
  
  
  The &lt;code&gt;ORDER BY&lt;/code&gt; Clause
&lt;/h4&gt;

&lt;p&gt;Within &lt;code&gt;OVER()&lt;/code&gt;, &lt;code&gt;ORDER BY&lt;/code&gt; determines the sequence in which rows are evaluated for functions such as &lt;code&gt;ROW_NUMBER()&lt;/code&gt;, &lt;code&gt;LAG()&lt;/code&gt;, and running totals.&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%2Fna6m5jqqia5a7zq88nql.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%2Fna6m5jqqia5a7zq88nql.png" alt=" " width="622" height="262"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Result:&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%2Fl770bj18vlx9w5d0oajq.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%2Fl770bj18vlx9w5d0oajq.png" alt=" " width="800" height="241"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What it does&lt;/strong&gt;: Numbers each patient's visits from earliest to latest. visit_id provides a tie-breaker when visits have the same date.&lt;br&gt;
&lt;strong&gt;Result&lt;/strong&gt;: Each patient's first recorded visit receives number 1, the second receives number 2, and so on.&lt;/p&gt;

&lt;h4&gt;
  
  
  Window Frames
&lt;/h4&gt;

&lt;p&gt;A window frame determines which rows SQL should include when performing a calculation for the current row. It is commonly used with functions such as SUM() and AVG() to calculate running totals and moving averages.&lt;br&gt;
Think of it this way: as SQL moves through hospital visits one row at a time, a window frame tells it which rows to include in the calculation for each visit.&lt;br&gt;
Example: Calculating running hospital revenue&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%2F5kfifc3w57l2a4ld22v9.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%2F5kfifc3w57l2a4ld22v9.png" alt=" " width="800" height="482"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;For each row, SQL adds the current bill to all the bills before it.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;ROWS specifies that the frame is based on rows.&lt;/li&gt;
&lt;li&gt;UNBOUNDED PRECEDING means start from the first row.&lt;/li&gt;
&lt;li&gt;CURRENT ROW means stop at the current row.
Therefore, the first row includes only its own bill, the second includes the first and second bills, and the third includes all three bills.
&lt;strong&gt;In summary&lt;/strong&gt;: A window frame tells SQL how far back or forward to look when calculating a value for each row. It is particularly useful for running totals, moving averages, and calculations involving a specific number of preceding or following records.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Ranking window functions
&lt;/h3&gt;

&lt;p&gt;Ranking functions assign positions to rows according to an ordering criterion. They are useful for identifying the most experienced doctors, highest-cost visits, or patients with the most visits.&lt;/p&gt;

&lt;h4&gt;
  
  
  ROW_NUMBER()
&lt;/h4&gt;

&lt;p&gt;&lt;strong&gt;Purpose&lt;/strong&gt;: Assigns a unique sequential number to each row within a partition.&lt;br&gt;
&lt;strong&gt;Hospital scenario&lt;/strong&gt;: Number each patient's visits in chronological order.&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%2Fa1f7zcjld3t454bfiyk2.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%2Fa1f7zcjld3t454bfiyk2.png" alt=" " width="525" height="216"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Result:&lt;br&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%2Fmwqpuk6uhuxzzrmsfqos.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%2Fmwqpuk6uhuxzzrmsfqos.png" alt=" " width="800" height="246"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Result&lt;/strong&gt;: A patient with four visits receives sequence numbers 1, 2, 3, and 4. Numbering restarts at 1 for the next patient.&lt;br&gt;
Even when two visits share the same date, visit_id helps establish a consistent order.&lt;/p&gt;

&lt;h4&gt;
  
  
  RANK()
&lt;/h4&gt;

&lt;p&gt;&lt;strong&gt;Purpose&lt;/strong&gt;: Assigns the same rank to tied values but skips subsequent rank numbers.&lt;br&gt;
&lt;strong&gt;Hospital scenario&lt;/strong&gt;: Rank doctors by years of experience.&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%2Fq3i8owwh7mi0hfiebpuh.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%2Fq3i8owwh7mi0hfiebpuh.png" alt=" " width="597" height="169"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Result:&lt;br&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%2Fr7rauugccfl61ydl5ti5.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%2Fr7rauugccfl61ydl5ti5.png" alt=" " width="800" height="177"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Result&lt;/strong&gt;: If the highest experience values are 20, 15, 15, and 10 years, their ranks are 1, 2, 2, and 4.&lt;br&gt;
This function is useful when tied doctors should share the same competitive position.&lt;/p&gt;

&lt;h4&gt;
  
  
  DENSE_RANK()
&lt;/h4&gt;

&lt;p&gt;&lt;strong&gt;Purpose:&lt;/strong&gt; Assigns equal ranks to ties without leaving gaps.&lt;br&gt;
Difference between &lt;code&gt;RANK()&lt;/code&gt; and &lt;code&gt;DENSE_RANK()&lt;/code&gt;: &lt;code&gt;RANK()&lt;/code&gt; skips numbers after ties, while &lt;code&gt;DENSE_RANK()&lt;/code&gt; continues with the next consecutive rank&lt;/p&gt;

&lt;h4&gt;
  
  
  NTILE()
&lt;/h4&gt;

&lt;p&gt;&lt;strong&gt;Purpose&lt;/strong&gt;: Divides ordered rows into a specified number of approximately equal-sized groups.&lt;br&gt;
&lt;strong&gt;Hospital scenario&lt;/strong&gt;: Segment visits into four billing tiers.&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%2Fiih75rewxkfclgd4qi93.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%2Fiih75rewxkfclgd4qi93.png" alt=" " width="508" height="190"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Result:&lt;br&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%2F7otww0fkf69zf3vvs0ew.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%2F7otww0fkf69zf3vvs0ew.png" alt=" " width="700" height="657"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Result&lt;/strong&gt;: Visits are distributed among four groups. Group 1 contains the highest bills, while group 4 contains the lowest bills, assuming descending order.&lt;br&gt;
This can help the hospital identify visits with comparatively high billing amounts.&lt;br&gt;
&lt;code&gt;NTILE(4)&lt;/code&gt; creates four groups of roughly equal numbers of rows, not four groups with equal billing values.&lt;/p&gt;

&lt;h3&gt;
  
  
  Value Window Functions
&lt;/h3&gt;

&lt;p&gt;Value functions retrieve values from particular rows within a window. They are especially useful for analyzing changes in patients' visits and identifying first or last recorded events.&lt;/p&gt;

&lt;h4&gt;
  
  
  LAG()
&lt;/h4&gt;

&lt;p&gt;&lt;strong&gt;Purpose&lt;/strong&gt;: Retrieves a value from a preceding row without requiring a self-join.&lt;br&gt;
&lt;strong&gt;Hospital scenario&lt;/strong&gt;: Compare each patient's current bill with their previous visit bill.&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%2Fkoiitx35vmfmjlnyn1by.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%2Fkoiitx35vmfmjlnyn1by.png" alt=" " width="511" height="241"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Result:&lt;br&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%2Fii2nwprlgv7rxrx01isn.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%2Fii2nwprlgv7rxrx01isn.png" alt=" " width="799" height="482"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Result&lt;/strong&gt;: The first visit for each patient has NULL in previous_visit_bill because no earlier visit exists. Subsequent visits show the preceding bill.&lt;br&gt;
For example, patient 1 has bills of KES 3,500 and KES 4,200, so the previous-bill values are NULL and 3,500.&lt;/p&gt;

&lt;h4&gt;
  
  
  LEAD()
&lt;/h4&gt;

&lt;p&gt;&lt;strong&gt;Purpose&lt;/strong&gt;: Retrieves a value from a following row in the window.&lt;br&gt;
&lt;strong&gt;Hospital scenario:&lt;/strong&gt; Examine the bill recorded for a patient's next visit.&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%2Fl07koga0mr5fpkhylzt2.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%2Fl07koga0mr5fpkhylzt2.png" alt=" " width="469" height="247"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Result:&lt;br&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%2F8w6r172bu46kueiyoyk1.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%2F8w6r172bu46kueiyoyk1.png" alt=" " width="800" height="511"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Result&lt;/strong&gt;: Each visit is displayed alongside the next recorded visit's bill for that patient. The last visit has NULL because no later visit exists.&lt;br&gt;
Difference between &lt;code&gt;LAG()&lt;/code&gt; and &lt;code&gt;LEAD()&lt;/code&gt;: &lt;code&gt;LAG()&lt;/code&gt; looks backward; &lt;code&gt;LEAD()&lt;/code&gt; looks forward.&lt;/p&gt;

&lt;h4&gt;
  
  
  FIRST_VALUE()
&lt;/h4&gt;

&lt;p&gt;&lt;strong&gt;Purpose&lt;/strong&gt;: Returns the value from the first row in the window frame.&lt;br&gt;
&lt;strong&gt;Hospital scenario&lt;/strong&gt;: Display the bill from each patient's earliest visit alongside every visit.&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%2Fkb12vnn7gfknq7zhasc7.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%2Fkb12vnn7gfknq7zhasc7.png" alt=" " width="511" height="286"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Result:&lt;br&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%2Fjass265ew963grnxprry.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%2Fjass265ew963grnxprry.png" alt=" " width="799" height="207"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h4&gt;
  
  
  LAST_VALUE()
&lt;/h4&gt;

&lt;p&gt;Purpose: Returns the value from the last row in the window frame.&lt;br&gt;
Hospital scenario: Display the bill from each patient's latest recorded visit alongside all their visits.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Result&lt;/strong&gt;: Every visit for a patient displays the bill from that patient's latest recorded visit.&lt;br&gt;
&lt;strong&gt;Important&lt;/strong&gt;: &lt;code&gt;LAST_VALUE()&lt;/code&gt; can be confusing because its result depends on the window frame. With an ordered default frame, it may return the current row's value or the last value among its peers rather than the final value in the entire partition. Specifying &lt;code&gt;UNBOUNDED FOLLOWING&lt;/code&gt; ensures that the frame extends to the partition's final row.&lt;/p&gt;

&lt;h3&gt;
  
  
  Aggregate Window Functions
&lt;/h3&gt;

&lt;p&gt;Aggregate window functions calculate summaries while preserving individual rows. They can support hospital revenue analysis, cost monitoring, and patient utilization reporting.&lt;/p&gt;

&lt;h4&gt;
  
  
  SUM()
&lt;/h4&gt;

&lt;p&gt;Purpose: Adds values within a window.&lt;br&gt;
Hospital scenario: Calculate the cumulative billing revenue generated by visits.&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%2F6ezyx1y8cwoxtsu0zc6f.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%2F6ezyx1y8cwoxtsu0zc6f.png" alt=" " width="549" height="250"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Result:&lt;br&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%2Fn9cyh86ezvpxq43p7z30.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%2Fn9cyh86ezvpxq43p7z30.png" alt=" " width="800" height="230"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;As shown in the results, starting with visit &lt;code&gt;1,001 (3,500 KES)&lt;/code&gt;, the cumulative revenue accumulates chronologically row by row—adding visit &lt;code&gt;1,002 (2,500 KES)&lt;/code&gt; to reach &lt;code&gt;6,000 KES, visit 1,003 (28,000 KES)&lt;/code&gt; to reach &lt;code&gt;34,000 KES&lt;/code&gt;, and so on,allowing hospital management to track financial growth alongside individual visit bills.   &lt;/p&gt;

&lt;h4&gt;
  
  
  AVG()
&lt;/h4&gt;

&lt;p&gt;Purpose: Calculates the average of values within a window.&lt;br&gt;
Hospital scenario: Compare every visit's bill with the average bill for that patient's visits.&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%2Fc44pqohvxbnd4dw6vkbw.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%2Fc44pqohvxbnd4dw6vkbw.png" alt=" " width="466" height="205"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Result:&lt;br&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%2F1g9l2geoisjf9rnp1418.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%2F1g9l2geoisjf9rnp1418.png" alt=" " width="800" height="227"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;As shown in the results, since Patient 1 has bills of &lt;code&gt;3,500 and 4,200&lt;/code&gt;, the average amount &lt;code&gt;(3,850)&lt;/code&gt; is displayed across both of their rows, allowing hospital staff to track individual visit bills alongside each patient's average treatment cost.&lt;/p&gt;

&lt;h4&gt;
  
  
  COUNT()
&lt;/h4&gt;

&lt;p&gt;&lt;strong&gt;Purpose&lt;/strong&gt;: Counts rows or non-NULL values within a window.&lt;br&gt;
&lt;strong&gt;Hospital scenario&lt;/strong&gt;: Count the recorded visits for each patient.&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%2Faxojljzhxucfplrlnda6.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%2Faxojljzhxucfplrlnda6.png" alt=" " width="406" height="193"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Result:&lt;br&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%2Fj73r4u0r9a37bbmgfgt6.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%2Fj73r4u0r9a37bbmgfgt6.png" alt=" " width="739" height="292"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;As shown in the results, since Patient 1 has two recorded visits &lt;code&gt;(1,001 and 1,011)&lt;/code&gt;, the total count of 2 is repeated next to both rows, allowing hospital staff to view granular visit logs alongside lifetime patient visit frequency.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;COUNT(*)&lt;/code&gt; counts rows, including rows containing NULL in individual columns. By contrast, COUNT(column_name) counts only rows where that column is not NULL.&lt;/p&gt;

&lt;h4&gt;
  
  
  MIN()
&lt;/h4&gt;

&lt;p&gt;&lt;strong&gt;Purpose&lt;/strong&gt;: Returns the minimum value in the window.&lt;br&gt;
&lt;strong&gt;Hospital scenario&lt;/strong&gt;: Identify the lowest recorded bill for each patient.&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%2Fhdg5v7xik5b0hqehsvo0.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%2Fhdg5v7xik5b0hqehsvo0.png" alt=" " width="484" height="201"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Result:&lt;br&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%2Fe1hqczquctkixqklofff.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%2Fe1hqczquctkixqklofff.png" alt=" " width="800" height="238"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;As shown in the results, Patient 1 has bills of &lt;code&gt;3,500 and 4,200&lt;/code&gt;, so the lowest amount &lt;code&gt;(3,500)&lt;/code&gt; is displayed across both of their rows, allowing hospital staff to track individual bills alongside each patient's lowest overall cost.&lt;/p&gt;

&lt;h4&gt;
  
  
  MAX()
&lt;/h4&gt;

&lt;p&gt;&lt;strong&gt;Purpose&lt;/strong&gt;: Returns the maximum value in the window.&lt;br&gt;
&lt;strong&gt;Hospital scenario&lt;/strong&gt;: Identify the highest recorded bill for each patient.&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%2Fn84ih9pazx06etz1s6gu.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%2Fn84ih9pazx06etz1s6gu.png" alt=" " width="427" height="201"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Result:&lt;br&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%2Fu43dj5k469sc1gs3ysfu.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%2Fu43dj5k469sc1gs3ysfu.png" alt=" " width="800" height="247"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;As shown in the results, since Patient 1 has bills of &lt;code&gt;3,500 and 4,200&lt;/code&gt;, the maximum amount &lt;code&gt;(4,200)&lt;/code&gt; is displayed across both of their rows, allowing hospital staff to track individual visit bills alongside each patient's peak treatment cost.&lt;/p&gt;

&lt;h2&gt;
  
  
  SQL Joins
&lt;/h2&gt;

&lt;p&gt;SQL joins combine related data from two or more tables using a specified condition. Hospital databases commonly store patient details, doctors, departments, and visits separately to reduce duplication and maintain data consistency.&lt;br&gt;
For example, the visits table contains identifiers such as patient_id, doctor_id, and department_id. Joins allow an analyst to retrieve meaningful names and department information instead of working only with identifiers.&lt;/p&gt;

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

&lt;p&gt;&lt;strong&gt;Purpose&lt;/strong&gt;: Returns only rows with matching values in both joined tables.&lt;br&gt;
&lt;strong&gt;Hospital scenario&lt;/strong&gt;: Retrieve visits with the corresponding patient details.&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%2F10wu4421bnnxgka44f0x.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%2F10wu4421bnnxgka44f0x.png" alt=" " width="451" height="199"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Result:&lt;br&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%2Fk5n1o7qruymw6cp92wda.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%2Fk5n1o7qruymw6cp92wda.png" alt=" " width="800" height="304"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What it does&lt;/strong&gt;: Matches every visit to the patient with the same patient_id.&lt;br&gt;
&lt;strong&gt;Result&lt;/strong&gt;: Visits with matching patient records appear in the output. Visits without a matching patient record are excluded.&lt;br&gt;
This is useful when an analysis requires complete matches between the tables.&lt;/p&gt;

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

&lt;p&gt;&lt;strong&gt;Purpose&lt;/strong&gt;: Returns every row from the left table and matching rows from the right table. Where no match exists, right-table columns contain NULL.&lt;br&gt;
&lt;strong&gt;Hospital scenario&lt;/strong&gt;: Find all registered patients, including those who have never visited.&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%2Fobi3efl473jkyuy6mje6.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%2Fobi3efl473jkyuy6mje6.png" alt=" " width="481" height="249"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Result:&lt;br&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%2Fkei3nex6vdw558v30x8p.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%2Fkei3nex6vdw558v30x8p.png" alt=" " width="800" height="232"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Result&lt;/strong&gt;: All patients are retained. Patients without visits have NULL for visit_id and visit_date.&lt;br&gt;
This is useful for identifying patients with no recorded visits and checking the completeness of hospital records.&lt;/p&gt;

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

&lt;p&gt;&lt;strong&gt;Purpose&lt;/strong&gt;: Returns every row from the right table and matching rows from the left table.&lt;br&gt;
&lt;strong&gt;Hospital scenario&lt;/strong&gt;: Retain every visit record, including visits without a matching patient record.&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%2Ftc64oz6wjk2nxa91hej5.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%2Ftc64oz6wjk2nxa91hej5.png" alt=" " width="498" height="202"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Result:&lt;br&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%2Fbgbrx5nivqghnvbnxgux.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%2Fbgbrx5nivqghnvbnxgux.png" alt=" " width="800" height="300"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Result&lt;/strong&gt;: Every row from visits appears. Where a visit has no matching patient record, the patient columns contain NULL.&lt;br&gt;
This can help audit inconsistent records, although a properly enforced foreign key should ordinarily prevent such orphaned visits.&lt;br&gt;
A &lt;code&gt;RIGHT JOIN&lt;/code&gt; can often be rewritten as a &lt;code&gt;LEFT JOIN&lt;/code&gt; by reversing the table order.&lt;/p&gt;

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

&lt;p&gt;&lt;strong&gt;Purpose:&lt;/strong&gt; Returns all matching rows plus unmatched rows from both tables.&lt;br&gt;
&lt;strong&gt;Hospital scenario&lt;/strong&gt;: Audit patient records and visit records to identify unmatched entries on either side.&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%2F2v916fmnijf69u5mi1xx.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%2F2v916fmnijf69u5mi1xx.png" alt=" " width="481" height="208"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Result:&lt;br&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%2Fb23y3vkctn0ordte68r3.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%2Fb23y3vkctn0ordte68r3.png" alt=" " width="800" height="240"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This is useful for data-quality investigations and reconciliation.&lt;/p&gt;

&lt;h3&gt;
  
  
  CROSS JOIN
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Purpose&lt;/strong&gt;: Returns every possible combination of rows from two tables, producing a Cartesian product.&lt;br&gt;
&lt;strong&gt;Hospital scenario&lt;/strong&gt;: Generate all possible combinations of departments and wards for planning or review.&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%2F6zd8rlsp9yahii0nxn7j.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%2F6zd8rlsp9yahii0nxn7j.png" alt=" " width="448" height="184"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Result:&lt;br&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%2Flh5fwi338x2wmhgdqd4u.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%2Flh5fwi338x2wmhgdqd4u.png" alt=" " width="570" height="282"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This query performs a CROSS JOIN between the department and wards tables, producing a Cartesian product where every single department is paired with every available ward. As shown in the results, each of the four departments &lt;code&gt;(Cardiology, General Medicine, Orthopedics, and Pediatrics)&lt;/code&gt; is combined with all four ward IDs &lt;code&gt;(100, 200, 300, and 400)&lt;/code&gt;, creating &lt;code&gt;16&lt;/code&gt; total combinations that allow hospital planners to evaluate every possible department-to-ward pairing.&lt;br&gt;
&lt;strong&gt;Caution&lt;/strong&gt;: A cross join can produce a very large result when the tables contain many rows.&lt;/p&gt;

&lt;h3&gt;
  
  
  SELF JOIN
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Purpose&lt;/strong&gt;: Joins a table to itself using different aliases.&lt;br&gt;
&lt;strong&gt;Hospital scenario&lt;/strong&gt;: Find pairs of doctors with the same number of years of experience.&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%2Fbj5txkbuxtj9rgeng5dk.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%2Fbj5txkbuxtj9rgeng5dk.png" alt=" " width="800" height="576"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The query performs a SELF JOIN on the doctors table by aliasing it as &lt;code&gt;doc1&lt;/code&gt; and &lt;code&gt;doc2&lt;/code&gt;, attempting to pair doctors who share the exact same number of experience years while eliminating duplicate reverse pairs using &lt;code&gt;doc1.doctor_id &amp;lt; doc2.doctor_id&lt;/code&gt;. As shown in the results, because all four doctors in the dataset currently possess unique experience years &lt;code&gt;(12, 9, 15, and 8 years&lt;/code&gt;respectively), no records satisfy the matching condition, resulting in an empty result set with zero rows returned.&lt;/p&gt;

&lt;h2&gt;
  
  
  Common Table Expressions (CTEs)
&lt;/h2&gt;

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

&lt;p&gt;A Common Table Expression (CTE) is a named result set defined using the WITH clause. It exists for the duration of the SQL statement and can make complex queries easier to understand, organize, and maintain.&lt;br&gt;
In hospital analytics, CTEs are useful for calculating department revenue, identifying high-cost visits, summarizing patient activity, and breaking complex reports into smaller logical steps.&lt;/p&gt;

&lt;h3&gt;
  
  
  Basic CTEs
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Purpose&lt;/strong&gt;: Give a query result a temporary name so it can be referenced by the main query.&lt;br&gt;
&lt;strong&gt;Hospital scenario&lt;/strong&gt;: Identify visits with bills exceeding &lt;code&gt;KES 10,000&lt;/code&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%2Fcatd140nzzzm4v8zs978.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%2Fcatd140nzzzm4v8zs978.png" alt=" " width="800" height="534"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What it does&lt;/strong&gt;: The CTE selects visits whose total bill exceeds &lt;code&gt;KES 10,000&lt;/code&gt;. The main query retrieves those records from the named result set.&lt;br&gt;
The CTE is not a permanent table. It is defined within this statement and can be referenced by the statement that follows it.&lt;/p&gt;

&lt;h3&gt;
  
  
  Multiple CTEs
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Purpose&lt;/strong&gt;: Break a complicated analysis into multiple named steps.&lt;br&gt;
&lt;strong&gt;Hospital scenario&lt;/strong&gt;: Calculate total billing revenue for each department and return departments with revenue above &lt;code&gt;KES 10,000&lt;/code&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%2F583u4d3tlc72irxauvss.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%2F583u4d3tlc72irxauvss.png" alt=" " width="490" height="555"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Result:&lt;br&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%2Fmq58p077pymho3249qi7.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%2Fmq58p077pymho3249qi7.png" alt=" " width="799" height="239"&gt;&lt;/a&gt;&lt;/p&gt;

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

&lt;ol&gt;
&lt;li&gt;
&lt;code&gt;department_visits&lt;/code&gt; joins department details to &lt;code&gt;individual visit billing records&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;department_revenue&lt;/code&gt; groups the records by &lt;code&gt;department&lt;/code&gt; and calculates &lt;code&gt;total revenue&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;The final query filters the summary to departments exceeding &lt;code&gt;KES 10,000&lt;/code&gt; and sorts them by revenue.
&lt;strong&gt;Result&lt;/strong&gt;: A list of qualifying departments, with their total billing revenue ordered from highest to lowest.
This structure is easier to maintain than placing every operation in one deeply nested query.&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  Recursive CTEs
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Purpose&lt;/strong&gt;: A recursive CTE repeatedly references its own result to process hierarchical data or generate sequences.&lt;br&gt;
&lt;strong&gt;Hospital scenario&lt;/strong&gt;:Generate a calendar containing every date between the hospital’s earliest and latest recorded visits. This helps identify days when patients visited the hospital and days when no visits were recorded&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%2F5u46ws53e52wsdtmc7tl.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%2F5u46ws53e52wsdtmc7tl.png" alt=" " width="543" height="291"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  CTEs combined with joins and aggregations
&lt;/h3&gt;

&lt;p&gt;CTEs can work with joins and aggregate functions to produce multi-stage hospital reports.&lt;br&gt;
&lt;strong&gt;Hospital scenario&lt;/strong&gt;: Count visits and calculate total billing revenue for each department.&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%2F6cayyc1zqgpp9toe14hp.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%2F6cayyc1zqgpp9toe14hp.png" alt=" " width="690" height="451"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Result:&lt;br&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%2Frv65guf7fycik6qx9cd1.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%2Frv65guf7fycik6qx9cd1.png" alt=" " width="800" height="202"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What it does&lt;/strong&gt;: The CTE summarizes visit activity for each department. The outer query joins those summaries to the department table.&lt;br&gt;
Expected result: Every department is listed, including departments without visits. Departments without visits display zero for total_visits and NULL for the revenue and average-bill fields.&lt;br&gt;
&lt;code&gt;COALESCE()&lt;/code&gt; replaces a NULL value with a specified alternative; here, it ensures that departments without recorded visits display zero visits.&lt;/p&gt;

&lt;h2&gt;
  
  
  SQL Subqueries
&lt;/h2&gt;

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

&lt;p&gt;A subquery is a query embedded within another SQL statement. It can appear in the &lt;code&gt;SELECT&lt;/code&gt;, &lt;code&gt;FROM&lt;/code&gt;, or &lt;code&gt;WHERE&lt;/code&gt; clause and can return a single value, a single column of values, or a table-like result.&lt;br&gt;
In hospital analytics, subqueries can identify patients with emergency visits, doctors with above-average workloads, and visits whose bills exceed specified benchmarks.&lt;/p&gt;

&lt;h3&gt;
  
  
  Scalar Subqueries
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Purpose&lt;/strong&gt;: Return a single value: one row and one column.&lt;br&gt;
&lt;strong&gt;Hospital scenario&lt;/strong&gt;: Display each visit alongside the average bill across all visits.&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%2Fhusxz9vht5z8o0hqzp6l.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%2Fhusxz9vht5z8o0hqzp6l.png" alt=" " width="799" height="491"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The inner query calculates the hospital-wide average bill. The outer query displays that value alongside each individual visit.&lt;br&gt;
Result: Each visit retains its own bill and has an additional column containing the overall average.&lt;br&gt;
A scalar subquery that returns more than one row causes an error when used in a scalar context.&lt;/p&gt;

&lt;h3&gt;
  
  
  Non-correlated Subqueries
&lt;/h3&gt;

&lt;p&gt;Run independently of the outer query; they do not reference columns from that query.&lt;br&gt;
&lt;strong&gt;Hospital scenario&lt;/strong&gt;: Identify visits whose bills exceed the hospital-wide average.&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%2F9j1003qqespa1drkao8e.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%2F9j1003qqespa1drkao8e.png" alt=" " width="700" height="582"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;How it works&lt;/strong&gt;: The inner query calculates the average once conceptually as an independent expression. The outer query compares each visit's bill with that average.&lt;br&gt;
Result: Only visits with bills greater than the overall average appear.&lt;/p&gt;

&lt;h3&gt;
  
  
  Correlated Subqueries
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Purpose&lt;/strong&gt;: Reference a column from the outer query, making the inner query dependent on the current outer row.&lt;br&gt;
&lt;strong&gt;Hospital scenario&lt;/strong&gt;: Count the total recorded visits for each doctor.&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%2Fatbxxmvh46blza34ro0m.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%2Fatbxxmvh46blza34ro0m.png" alt=" " width="706" height="532"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What it does&lt;/strong&gt;: For each doctor, the subquery counts records in visits that have the same doctor_id.&lt;br&gt;
Result: Every doctor appears with their recorded visit count. Doctors without visits receive a count of zero.&lt;br&gt;
The reference d.doctor_id connects the inner query to the current doctor in the outer query. This is what makes the subquery correlated.&lt;/p&gt;

&lt;h3&gt;
  
  
  Nested Subqueries
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Purpose&lt;/strong&gt;: Place a subquery inside another subquery to perform multiple levels of filtering or calculation.&lt;br&gt;
&lt;strong&gt;Hospital scenario&lt;/strong&gt;: Find visits whose bills exceed the average bill for visits belonging to patients who have had at least two recorded visits.&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%2Fc8olzvfltzst7wvfd6q2.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%2Fc8olzvfltzst7wvfd6q2.png" alt=" " width="709" height="739"&gt;&lt;/a&gt;&lt;/p&gt;

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

&lt;ol&gt;
&lt;li&gt;The innermost query groups visits by patient and identifies patients with at least two visits.&lt;/li&gt;
&lt;li&gt;The middle query calculates the average bill across visits belonging to those patients.&lt;/li&gt;
&lt;li&gt;The outer query returns visits whose bills exceed that calculated average.
Result: A list of visits with bills above the average for the specified group of patients.
This query demonstrates nested filtering, although a CTE might make the same logic easier to read in a larger reporting task.&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  Subqueries using &lt;code&gt;IN&lt;/code&gt;
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Purpose&lt;/strong&gt;: Test whether a value matches any value returned by a subquery.&lt;br&gt;
&lt;strong&gt;Hospital scenario&lt;/strong&gt;: Find patients who have had an emergency admission.&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%2Fsv46ggb1qfezgjpndub1.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%2Fsv46ggb1qfezgjpndub1.png" alt=" " width="675" height="480"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What it does&lt;/strong&gt;: The inner query identifies patients with an emergency visit. The outer query retrieves their demographic information.&lt;br&gt;
Result: Patients with at least one matching emergency visit are returned. A patient with multiple emergency visits still appears only once in this outer query because the outer table is patients.&lt;/p&gt;

&lt;h3&gt;
  
  
  Subqueries using &lt;code&gt;EXISTS&lt;/code&gt;
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Purpose&lt;/strong&gt;: Test whether a subquery returns at least one row.&lt;br&gt;
&lt;strong&gt;Hospital scenario&lt;/strong&gt;: Find patients who have at least one recorded visit.&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%2Fvqqzt5wfzifgk2hyl42u.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%2Fvqqzt5wfzifgk2hyl42u.png" alt=" " width="546" height="604"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What it does&lt;/strong&gt;: For each patient, the subquery checks whether a matching visit exists.&lt;br&gt;
Result: Only patients with at least one recorded visit are returned.&lt;/p&gt;

&lt;h3&gt;
  
  
  Subqueries using &lt;code&gt;ANY&lt;/code&gt;
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Purpose&lt;/strong&gt;: Compare a value with the results of a subquery and return true if the comparison is true for at least one returned value.&lt;br&gt;
&lt;strong&gt;Hospital scenario&lt;/strong&gt;: Find visits whose bills exceed at least one bill recorded for a particular patient, here patient ID 1.&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%2F5i6p2tpt3briam2mmqfk.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%2F5i6p2tpt3briam2mmqfk.png" alt=" " width="777" height="661"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What it does&lt;/strong&gt;: Returns visits whose bills are greater than at least one non-NULL bill belonging to patient 1.&lt;br&gt;
&lt;strong&gt;Result&lt;/strong&gt;: A visit qualifies if its bill exceeds at least one bill in the comparison set.&lt;br&gt;
For example, if patient 1 has bills of KES 2,000 and KES 5,000, a bill of KES 3,000 satisfies &amp;gt; ANY because it exceeds KES 2,000, even though it does not exceed KES 5,000.&lt;br&gt;
If the subquery returns no rows, the ANY comparison is false.&lt;/p&gt;

&lt;h3&gt;
  
  
  Subqueries using &lt;code&gt;ALL&lt;/code&gt;
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Purpose&lt;/strong&gt;: Compare a value with every value returned by a subquery.&lt;br&gt;
&lt;strong&gt;Hospital scenario&lt;/strong&gt;: Find visits whose bills exceed every non-NULL bill recorded for patient 1.&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%2Fpmgwwsmticuy9emqtxhj.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%2Fpmgwwsmticuy9emqtxhj.png" alt=" " width="745" height="634"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What it does&lt;/strong&gt;: Returns visits whose bills are greater than the highest bill in the comparison set, provided the set is nonempty.&lt;br&gt;
&lt;strong&gt;Result&lt;/strong&gt;: If patient 1's bills are KES 2,000 and KES 5,000, a visit with a bill of KES 6,000 qualifies, but a bill of KES 3,000 does not.&lt;br&gt;
If the subquery returns no rows, the ALL comparison is true by definition. This is an important difference to consider when writing filters.&lt;/p&gt;

&lt;h3&gt;
  
  
  Subqueries in &lt;code&gt;SELECT&lt;/code&gt;, &lt;code&gt;FROM&lt;/code&gt;, and &lt;code&gt;WHERE&lt;/code&gt;
&lt;/h3&gt;

&lt;p&gt;Subqueries can be positioned in different parts of a SQL statement depending on the type of result.&lt;/p&gt;

&lt;h4&gt;
  
  
  Subquery in &lt;code&gt;SELECT&lt;/code&gt;
&lt;/h4&gt;

&lt;p&gt;A scalar subquery can provide an additional calculated 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%2Fk7yhkrhxplcv6zbkjk5h.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%2Fk7yhkrhxplcv6zbkjk5h.png" alt=" " width="712" height="529"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h4&gt;
  
  
  Subquery in &lt;code&gt;FROM&lt;/code&gt;
&lt;/h4&gt;

&lt;p&gt;A subquery in the FROM clause creates a derived table that the outer query can process.&lt;br&gt;
&lt;strong&gt;Hospital scenario&lt;/strong&gt;: Calculate each patient's total billing, then find the average total billing per patient.&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%2F6cr3lezncxacaae9qw0u.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%2F6cr3lezncxacaae9qw0u.png" alt=" " width="708" height="433"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What it does&lt;/strong&gt;: The inner query calculates total bills for each patient. The outer query averages those patient-level totals.&lt;br&gt;
&lt;strong&gt;Result&lt;/strong&gt;: A single value representing the average total billing per patient among patients represented in the derived table.&lt;br&gt;
This differs from the average individual visit bill because each patient is first represented by their cumulative billing total.&lt;/p&gt;

&lt;h4&gt;
  
  
  Subquery in &lt;code&gt;WHERE&lt;/code&gt;
&lt;/h4&gt;

&lt;p&gt;A subquery can filter records based on a comparison or membership condition.&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%2Fagg62061maw51vc2je10.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%2Fagg62061maw51vc2je10.png" alt=" " width="765" height="589"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Return visits meeting a condition derived from another query, such as exceeding the overall average bill.&lt;/p&gt;

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

&lt;p&gt;Advanced PostgreSQL techniques enable healthcare analysts to transform relational data into meaningful operational and financial insights.&lt;br&gt;
&lt;strong&gt;Window functions&lt;/strong&gt; support ranking, comparisons, and cumulative calculations while preserving individual records. &lt;br&gt;
&lt;strong&gt;Joins&lt;/strong&gt; combine related information across tables and help identify unmatched records. &lt;br&gt;
&lt;strong&gt;CTEs&lt;/strong&gt; organize complex queries into manageable stages and support recursive processing. Subqueries allow filtering and calculations based on the results of other queries.&lt;br&gt;
The most effective SQL solution depends on the question being asked, the structure of the data, and the expected output. Understanding not only how to write a query but also why it works is essential for producing reliable and useful analysis.&lt;/p&gt;

</description>
      <category>database</category>
      <category>sql</category>
      <category>tutorial</category>
    </item>
    <item>
      <title>Power BI Data Modelling, Relationships and Joins.</title>
      <dc:creator>Mercy Siele</dc:creator>
      <pubDate>Sun, 13 Sep 2026 15:13:00 +0000</pubDate>
      <link>https://dev.to/siele/power-bi-data-modelling-relationships-and-joins-4bbj</link>
      <guid>https://dev.to/siele/power-bi-data-modelling-relationships-and-joins-4bbj</guid>
      <description>&lt;h1&gt;
  
  
  Introduction
&lt;/h1&gt;

&lt;p&gt;When I first started working with Power BI, I thought that importing data and creating charts was the main part of building a report. However, I quickly realized that before creating visuals, I needed to understand how my data was structured and how different tables were connected.&lt;/p&gt;

&lt;p&gt;A Power BI report can contain beautiful dashboards, but if the underlying data model is poorly designed, the report can become difficult to maintain, DAX calculations can become unnecessarily complicated, and filters may not behave as expected.&lt;/p&gt;

&lt;p&gt;This article explores three important areas of Power BI:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Data modelling and schema design&lt;/li&gt;
&lt;li&gt;Relationships between tables&lt;/li&gt;
&lt;li&gt;Joins in Power Query&lt;/li&gt;
&lt;/ul&gt;

&lt;h1&gt;
  
  
  Data Modelling in Power BI.
&lt;/h1&gt;

&lt;p&gt;Data modelling is the process of organizing tables and defining how they relate to each other so that Power BI can correctly analyze the data.&lt;/p&gt;

&lt;p&gt;Instead of putting every piece of information into one large table, we can separate information into logical tables and connect them using relationships.&lt;br&gt;
For example, a sales business might have:&lt;br&gt;
Customers&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Products&lt;/li&gt;
&lt;li&gt;Dates&lt;/li&gt;
&lt;li&gt;Locations&lt;/li&gt;
&lt;li&gt;FactSales&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The &lt;code&gt;FactSales table&lt;/code&gt; contains the actual sales transactions, while the other tables provide descriptive information about customers, products, dates, and locations.&lt;br&gt;
A good data model is important because it affects:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;- Reporting &amp;amp; Analytics:&lt;/strong&gt; Keeps your data organized so finding and dragging fields into charts feels natural and quick.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;- DAX Calculations:&lt;/strong&gt; Allows you to write short, simple formulas instead of long, confusing code to connect messy tables.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;- Performance:&lt;/strong&gt; Helps Power BI run much faster, so charts load instantly instead of freezing or lagging.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;- Scalability:&lt;/strong&gt; Keeps your reports fast and stable, even as your data grows from thousands of rows to millions.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;- Maintainability:&lt;/strong&gt; Makes it easy to fix mistakes, update numbers, or share your work with teammates without breaking anything.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The Dataset Used in This Project&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;For this project, I used a small sales dataset.&lt;br&gt;
The workbook contains the following tables:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Table&lt;/th&gt;
&lt;th&gt;Purpose&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Customers&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Customer information&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;CustomerProfiles&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Customer contact information&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Segments&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Customer segment information&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Products&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Product information&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Locations&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Location information&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Dates&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Date attributes&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Orders&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Order information&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;OrderDetails&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Products contained in each order&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;FactSales&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Central sales fact table&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;
&lt;h2&gt;
  
  
  Data Modelling Approaches
&lt;/h2&gt;

&lt;p&gt;There are several ways to organize data in a Power BI model.&lt;br&gt;
The three approaches discussed in this article are:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Flat Table&lt;/li&gt;
&lt;li&gt;Star Schema&lt;/li&gt;
&lt;li&gt;Snowflake Schema&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;
  
  
  Flat Table
&lt;/h2&gt;

&lt;p&gt;A flat table contains most or all of the information required for analysis in a single table.Instead of having separate Customer, Product, Location and Sales tables, the information is stored together.&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%2Fw3asnrdmn3sdb0v5qc7w.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%2Fw3asnrdmn3sdb0v5qc7w.png" alt=" " width="800" height="457"&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%2Frke4w9igfoemb2yk09vv.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%2Frke4w9igfoemb2yk09vv.png" alt=" " width="798" height="181"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Advantages of a Flat Table&lt;/strong&gt;&lt;br&gt;
A flat table is relatively easy to understand, especially for beginners.&lt;br&gt;
Advantages include:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Simple structure&lt;/li&gt;
&lt;li&gt;No relationships required&lt;/li&gt;
&lt;li&gt;Easy to create simple visuals&lt;/li&gt;
&lt;li&gt;Easy to export&lt;/li&gt;
&lt;li&gt;Useful for small datasets&lt;/li&gt;
&lt;li&gt;Suitable for straightforward analysis&lt;/li&gt;
&lt;/ul&gt;

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

&lt;p&gt;The biggest problem is data redundancy.&lt;br&gt;
For example, if a customer makes 100 purchases, their name, city, county and other descriptive information may be repeated 100 times.&lt;/p&gt;

&lt;p&gt;This can result in:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Larger models&lt;/li&gt;
&lt;li&gt;Repeated data&lt;/li&gt;
&lt;li&gt;More difficult maintenance&lt;/li&gt;
&lt;li&gt;More opportunities for inconsistent information&lt;/li&gt;
&lt;li&gt;Less scalable models&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;A &lt;code&gt;flat table&lt;/code&gt; is therefore appropriate when the dataset is small, the analysis is simple, the data already comes in a clean denormalized format, there are no complex relationships, or the report is temporary or exploratory.&lt;/p&gt;
&lt;h2&gt;
  
  
  Star Schema
&lt;/h2&gt;

&lt;p&gt;A star schema separates the business event from the descriptive information.&lt;br&gt;
At the center is a fact table, surrounded by dimension tables.&lt;br&gt;
For this project, the model can be structured as:&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%2F8l5drrsb4qw7jv2ct6r6.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%2F8l5drrsb4qw7jv2ct6r6.png" alt=" " width="800" height="444"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Advantages of a Star Schema&lt;/strong&gt;&lt;br&gt;
A star schema has several advantages:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Easy to understand&lt;/li&gt;
&lt;li&gt;Clear separation between facts and dimensions&lt;/li&gt;
&lt;li&gt;Simple relationships&lt;/li&gt;
&lt;li&gt;DAX is generally easier to write&lt;/li&gt;
&lt;li&gt;Good report performance&lt;/li&gt;
&lt;li&gt;Easy filter propagation&lt;/li&gt;
&lt;li&gt;Scales well&lt;/li&gt;
&lt;li&gt;Easier to maintain&lt;/li&gt;
&lt;li&gt;Reduces unnecessary duplication&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Disadvantages&lt;/strong&gt;&lt;br&gt;
There are some disadvantages:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;More tables need to be managed&lt;/li&gt;
&lt;li&gt;Relationships must be correctly configured&lt;/li&gt;
&lt;li&gt;Beginners may initially find the model more complicated than a flat table&lt;/li&gt;
&lt;li&gt;Poor relationship design can produce incorrect results&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;When Should a Star Schema Be Used?&lt;/strong&gt;&lt;br&gt;
A star schema is particularly useful for:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Business intelligence&lt;/li&gt;
&lt;li&gt;Sales reporting&lt;/li&gt;
&lt;li&gt;Financial reporting&lt;/li&gt;
&lt;li&gt;Inventory analysis&lt;/li&gt;
&lt;li&gt;Customer analytics&lt;/li&gt;
&lt;li&gt;Operational dashboards&lt;/li&gt;
&lt;li&gt;Large analytical models&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;
  
  
  Snowflake Schema
&lt;/h3&gt;

&lt;p&gt;A snowflake schema is similar to a star schema, but dimension tables are further divided into additional related tables.&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%2Fiyp19tg98r4srhrpjwpg.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%2Fiyp19tg98r4srhrpjwpg.png" alt=" " width="799" height="300"&gt;&lt;/a&gt;&lt;br&gt;
&lt;strong&gt;Advantages&lt;/strong&gt;&lt;br&gt;
A snowflake schema can:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Reduce repeated descriptive data&lt;/li&gt;
&lt;li&gt;Normalize dimensions&lt;/li&gt;
&lt;li&gt;Improve consistency in some situations&lt;/li&gt;
&lt;li&gt;Be useful when dimensions have complex hierarchies&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Disadvantages&lt;/strong&gt;&lt;br&gt;
The main disadvantage is increased complexity.&lt;br&gt;
This includes:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;More tables&lt;/li&gt;
&lt;li&gt;More relationships&lt;/li&gt;
&lt;li&gt;More joins/relationship paths&lt;/li&gt;
&lt;li&gt;Potentially more complicated DAX&lt;/li&gt;
&lt;li&gt;More complicated filter propagation&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;It can be therefore be approriate when:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Dimensions are very large&lt;/li&gt;
&lt;li&gt;Hierarchies are complex&lt;/li&gt;
&lt;li&gt;Data is already highly normalized&lt;/li&gt;
&lt;li&gt;Reducing redundancy is important&lt;/li&gt;
&lt;li&gt;There are reusable sub-dimensions&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;
  
  
  Understanding Fact Tables and Dimension Tables
&lt;/h2&gt;

&lt;p&gt;One of the most important concepts in data modelling is understanding the difference between fact tables and dimension tables.&lt;br&gt;
A &lt;strong&gt;fact table&lt;/strong&gt; stores information about business events or transactions.It contains numerical values that can be aggregated.&lt;br&gt;
In my data set the &lt;code&gt;Fact Sales&lt;/code&gt; is the fact table containing OrderID,OrderDate,CustomerID,ProductID,LocationID,Quantity,Price, Revenue,Cost,Profit and Status.&lt;br&gt;
&lt;strong&gt;Dimension tables&lt;/strong&gt; contain descriptive attributes used to describe and filter the business events in the fact table. Examples include:CustomerID,CustomerName,City,County and SegmentID.&lt;br&gt;
In simple terms:&lt;br&gt;
Dimensions answer questions such as:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Who?&lt;/li&gt;
&lt;li&gt;What?&lt;/li&gt;
&lt;li&gt;Where?&lt;/li&gt;
&lt;li&gt;When?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Facts answer questions such as:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;How many?&lt;/li&gt;
&lt;li&gt;How much?&lt;/li&gt;
&lt;li&gt;What revenue?&lt;/li&gt;
&lt;li&gt;What cost?&lt;/li&gt;
&lt;li&gt;What profit?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Understanding Granularity (Grain) in Data Modeling&lt;/strong&gt;&lt;br&gt;
When designing a data model, one of the first questions you should ask is:&lt;br&gt;
&lt;code&gt;What does one row in this table represent?&lt;/code&gt;&lt;br&gt;
The answer to this question is called the grain of the table.&lt;/p&gt;

&lt;p&gt;Grain defines the exact level of detail stored in each row of a fact table. In other words, it tells us what a single record represents.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Practical Example: Building a Star Schema for a Retail Business&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Imagine a retail company that sells clothing through several branches. The business wants to use Power BI to analyze its sales performance.Instead of putting all the information into one large table, we can organize the data using a &lt;strong&gt;star schema&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;At the center of the model is the &lt;code&gt;Fact Sales&lt;/code&gt; table. This table contains the sales transactions and the numerical information we want to analyze, such as &lt;code&gt;quantity sold&lt;/code&gt;, &lt;code&gt;sales revenue&lt;/code&gt;, and &lt;code&gt;discounts&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Around the fact table are several &lt;code&gt;dimension tables&lt;/code&gt; that provide additional information about the sales, such as &lt;code&gt;Dim Customer&lt;/code&gt;, &lt;code&gt;Dim Product&lt;/code&gt;, &lt;code&gt;Dim Store&lt;/code&gt;, and &lt;code&gt;Dim Date&lt;/code&gt;.&lt;/p&gt;
&lt;h2&gt;
  
  
  Connecting the Tables Using Keys
&lt;/h2&gt;

&lt;p&gt;We can use the &lt;strong&gt;Fact Sales&lt;/strong&gt; and &lt;strong&gt;Dim Product&lt;/strong&gt; tables to see how the connection works.&lt;br&gt;
The &lt;code&gt;Product_Key&lt;/code&gt; in &lt;strong&gt;Dim Product&lt;/strong&gt; is the &lt;strong&gt;primary key&lt;/strong&gt;. It uniquely identifies each product in the table.&lt;/p&gt;

&lt;p&gt;The &lt;code&gt;Product_Key&lt;/code&gt; in &lt;strong&gt;Fact Sales&lt;/strong&gt; is the &lt;strong&gt;foreign key&lt;/strong&gt;. It is used to link each sale to the correct product in the Dim Product table.&lt;/p&gt;

&lt;p&gt;For example, we could have:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Dim Product&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;Product_Key&lt;/th&gt;
&lt;th&gt;Product_Name&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;P001&lt;/td&gt;
&lt;td&gt;Classic Shirt&lt;/td&gt;
&lt;td&gt;Shirts&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;P002&lt;/td&gt;
&lt;td&gt;Wide Leg Pants&lt;/td&gt;
&lt;td&gt;Trousers&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;Fact Sales&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;Sale_ID&lt;/th&gt;
&lt;th&gt;Product_Key&lt;/th&gt;
&lt;th&gt;Quantity&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;1001&lt;/td&gt;
&lt;td&gt;P001&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;8,000&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;1002&lt;/td&gt;
&lt;td&gt;P002&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;5,000&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;1003&lt;/td&gt;
&lt;td&gt;P001&lt;/td&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;td&gt;12,000&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;These two tables are connected through &lt;code&gt;Product_Key&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;If Power BI sees &lt;code&gt;P001&lt;/code&gt; in the Fact Sales table, it can use that key to find &lt;code&gt;P001&lt;/code&gt; in the Dim Product table and identify the product as &lt;code&gt;Classic Shirt&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;The relationship is:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Dim Product (1) → Fact Sales (*)&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;This means that one product can appear in many sales transactions.&lt;/p&gt;
&lt;h2&gt;
  
  
  Understanding the Keys
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Primary Key:&lt;/strong&gt; A column that uniquely identifies each record in a table. In our example, &lt;code&gt;Product_Key&lt;/code&gt; uniquely identifies each product in &lt;code&gt;Dim Product&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Foreign Key:&lt;/strong&gt; A column that refers to the primary key of another table. In our example, &lt;code&gt;Product_Key&lt;/code&gt; in &lt;code&gt;Fact Sales&lt;/code&gt; links each sale to a product in &lt;code&gt;Dim Product&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The same approach can be used to connect other dimension tables, such as &lt;strong&gt;Dim Customer, Dim Store, and Dim Date&lt;/strong&gt;, to the Fact Sales table. Together, these tables form a &lt;strong&gt;star schema&lt;/strong&gt;.&lt;/p&gt;
&lt;h2&gt;
  
  
  Relationships in Power BI
&lt;/h2&gt;

&lt;p&gt;A relationship tells Power BI how two tables are connected.Relationships are necessary because analytical information is often distributed across multiple tables.&lt;/p&gt;
&lt;h3&gt;
  
  
  One-to-Many Relationship (1:*)
&lt;/h3&gt;

&lt;p&gt;The most common relationship in a Power BI star schema is one-to-many.A single unique key in one table (the "One" side) matches multiple instances of that same key in another table (the "Many" side).&lt;br&gt;
&lt;strong&gt;Practical Example:&lt;/strong&gt; Connecting DimCustomer[CustomerID] to FactSales[CustomerID]. A customer registers once (unique on the "One" side), but can make multiple purchases over time (appears multiple times on the "Many" side).&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;When to use:&lt;/strong&gt; This is the gold standard for Power BI data modeling. Use it to connect all dimension tables to your central fact table.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;When NOT to use:&lt;/strong&gt; Never force a $1:* relationship if the key on the "One" side contains duplicates; Power BI will throw an error or default to a Many-to-Many relationship.&lt;/p&gt;
&lt;h3&gt;
  
  
  One-to-One Relationship(1:1)
&lt;/h3&gt;

&lt;p&gt;Each unique value in the primary key column of Table A corresponds to exactly one unique value in Table B, and vice versa.&lt;br&gt;
&lt;strong&gt;Practical Example:&lt;/strong&gt; Connecting DimCustomer to CustomerContactInfo, where both tables use CustomerID as a unique primary key.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;When to use:&lt;/strong&gt; **Use sparingly when splitting a very wide table into two for security reasons (e.g., separating sensitive HR data) or organizational performance.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;When NOT to use:&lt;/strong&gt; In almost all standard Power BI scenarios, 1:1 relationships add unnecessary model complexity. It is far better to merge the two tables into a single table using Power Query before loading the model.&lt;/p&gt;
&lt;h3&gt;
  
  
  Many-to-Many Relationship(&lt;em&gt;:&lt;/em&gt;)
&lt;/h3&gt;

&lt;p&gt;Neither column in the relationship contains unique values. Multiple records in Table A match multiple records in Table B.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Practical Example:&lt;/strong&gt; Connecting FactSales directly to a Promotions table where a single sale can qualify for multiple promotions, and a single promotion applies to multiple sales transactions.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;When to use:&lt;/strong&gt; Use only when modeling complex, non-additive business scenarios that cannot be restructured in Power Query (such as target vs. actuals at higher aggregation levels).&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;When NOT to use:&lt;/strong&gt; Avoid using direct &lt;em&gt;:&lt;/em&gt; relationships between standard facts and dimensions. They introduce ambiguous filter paths, create unpredictable DAX calculation results, and significantly reduce query performance. Instead, resolve them by introducing a Bridge Table in between to create two 1:* relationships.&lt;/p&gt;
&lt;h2&gt;
  
  
  Other Important Relationship Concepts
&lt;/h2&gt;
&lt;h4&gt;
  
  
  Primary Keys and Foreign Keys
&lt;/h4&gt;

&lt;p&gt;A primary key is a column that uniquely identifies each record in a table. In the Customers table, CustomerID acts as the primary key because each customer has a unique ID.&lt;br&gt;
A foreign key is a column that references a key in another table. In FactSales, CustomerID acts as a foreign key.&lt;br&gt;
Unlike the primary key, the foreign key does not have to be unique.&lt;/p&gt;
&lt;h4&gt;
  
  
  Cardinality
&lt;/h4&gt;

&lt;p&gt;Cardinality describes how many records in one table can be associated with records in another table.&lt;br&gt;
Power BI supports several types of cardinality, with three being particularly important when designing a model.&lt;/p&gt;
&lt;h4&gt;
  
  
  Unique Values and Referential Integrity
&lt;/h4&gt;

&lt;p&gt;For a column to be used on the one side of a 1:* relationship, its values must be unique.&lt;/p&gt;

&lt;p&gt;For example:&lt;br&gt;
&lt;code&gt;Customers[CustomerID]&lt;br&gt;
C001&lt;br&gt;
C002&lt;br&gt;
C003&lt;br&gt;
C004&lt;br&gt;
C005&lt;br&gt;
C006&lt;/code&gt;&lt;br&gt;
There should not be multiple records with C001 in this table.&lt;/p&gt;

&lt;p&gt;The corresponding foreign key in FactSales, however, can contain repeated values:&lt;br&gt;
&lt;code&gt;FactSales[CustomerID]&lt;br&gt;
C001&lt;br&gt;
C002&lt;br&gt;
C003&lt;br&gt;
C001&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Referential integrity&lt;/strong&gt; refers to the consistency between the values in the related tables. Ideally, every foreign-key value in the fact table should have a corresponding key in the dimension table.For example:&lt;br&gt;
&lt;code&gt;FactSales[CustomerID] → Customers[CustomerID]&lt;/code&gt;&lt;/p&gt;
&lt;h4&gt;
  
  
  Active and Inactive Relationships
&lt;/h4&gt;

&lt;p&gt;Power BI relationships can be active or inactive.&lt;br&gt;
An active relationship is used automatically when filters and calculations are evaluated.&lt;/p&gt;

&lt;p&gt;For example:&lt;code&gt;DimDate[Date] ─────── FactSales[OrderDate]&lt;br&gt;
                              Active&lt;/code&gt;&lt;br&gt;
If a report user selects January 2026 from DimDate, the active relationship allows Power BI to filter the relevant sales records.&lt;br&gt;
An &lt;strong&gt;inactive&lt;/strong&gt; relationship exists in the model but is not automatically used.This can be useful when a fact table contains multiple date columns, such as:&lt;br&gt;
                  &lt;code&gt;FactSales&lt;br&gt;
                   ---------&lt;br&gt;
                   OrderDate&lt;br&gt;
                   ShipDate&lt;br&gt;
                   DeliveryDate&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;A model could contain relationships from a Date dimension to both OrderDate and ShipDate, while only one is active. The inactive relationship can then be used when a calculation specifically needs the alternative date.&lt;/p&gt;
&lt;h1&gt;
  
  
  Filter Direction in Power BI
&lt;/h1&gt;

&lt;p&gt;Relationships determine not only how tables are connected but also how filters move between them.&lt;br&gt;
Consider the following relationship:&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%2Fqlgyvyy0dsa20uv4kyv7.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%2Fqlgyvyy0dsa20uv4kyv7.png" alt=" " width="460" height="162"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;If a user selects &lt;strong&gt;Bags&lt;/strong&gt; from &lt;code&gt;DimProduct&lt;/code&gt;, the filter travels to &lt;code&gt;FactSales&lt;/code&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%2Fm9ski469ax2ntp07pnvy.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%2Fm9ski469ax2ntp07pnvy.png" alt=" " width="243" height="267"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h2&gt;
  
  
  Single-Direction Filtering
&lt;/h2&gt;

&lt;p&gt;In a typical star schema, filters flow from the dimension tables toward the fact table.&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%2Fejuk155pkqydbtnhv7ti.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%2Fejuk155pkqydbtnhv7ti.png" alt=" " width="462" height="162"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;For example, selecting: Product Category = Bags,&lt;br&gt;
filters the products in &lt;code&gt;DimProduct&lt;/code&gt;, which then filters the related records in &lt;code&gt;FactSales&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Single-direction filtering is generally preferred because the direction of filter propagation is clear and predictable.&lt;/p&gt;
&lt;h2&gt;
  
  
  Bidirectional Filtering
&lt;/h2&gt;

&lt;p&gt;With bidirectional filtering, filters can flow in both directions between related tables.&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%2Fl6o0h5dliz7nuvq1xy5l.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%2Fl6o0h5dliz7nuvq1xy5l.png" alt=" " width="225" height="121"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Bidirectional filtering can be useful for specific modelling requirements, but it should not automatically be enabled throughout a model.&lt;br&gt;
onsider a model containing several interconnected tables:&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%2Fhefxie3fo9i8voubjitv.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%2Fhefxie3fo9i8voubjitv.png" alt=" " width="496" height="123"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;There may now be multiple paths through which a filter can travel.&lt;/p&gt;

&lt;p&gt;This can result in:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Ambiguous filter paths&lt;/li&gt;
&lt;li&gt;Unexpected filtering&lt;/li&gt;
&lt;li&gt;More difficult DAX calculations&lt;/li&gt;
&lt;li&gt;Harder-to-understand report behaviour&lt;/li&gt;
&lt;li&gt;Unnecessary model complexity&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;For this reason, my default approach would be &lt;strong&gt;single-direction&lt;/strong&gt; filtering from dimension tables to fact tables, unless there is a clear business requirement for bidirectional filtering.&lt;/p&gt;
&lt;h1&gt;
  
  
  Joins in Power Query
&lt;/h1&gt;

&lt;p&gt;A join combines information from two tables based on one or more matching columns.&lt;br&gt;
In Power Query, joins are performed using Merge Queries.&lt;br&gt;
For example, consider two tables from the practice dataset.&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%2F2mhxazijjc101zs8t3m1.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%2F2mhxazijjc101zs8t3m1.png" alt=" " width="721" height="300"&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%2F1pibw7uc964jso2an1dw.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%2F1pibw7uc964jso2an1dw.png" alt=" " width="591" height="339"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The common column is &lt;code&gt;CustomerID&lt;/code&gt;.&lt;br&gt;
Different join types produce different results depending on which records we want to retain.&lt;/p&gt;
&lt;h2&gt;
  
  
  Left Outer Join
&lt;/h2&gt;

&lt;p&gt;A Left Outer Join keeps all records from the left (First) table and any matching records from the right (Second) table.&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%2F884jd01tqbo1ob64qi8k.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%2F884jd01tqbo1ob64qi8k.png" alt=" " width="800" height="195"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;I used Customers as the left table and Orders as the right table. The Left Outer Join retained every customer, including C006, even though this customer had no corresponding order. The order information was added where a matching CustomerID existed.&lt;/p&gt;
&lt;h2&gt;
  
  
  Right Outer Join
&lt;/h2&gt;

&lt;p&gt;Keeps every row from the right (second) table, plus matches from the left( First) table where they exist.&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%2F3bnhk6b7caq3w02s64kl.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%2F3bnhk6b7caq3w02s64kl.png" alt=" " width="799" height="186"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The Right Outer Join retained every record from the Orders table. As a result, O1008 remained in the output even though its CustomerID, C999, did not exist in the Customers table.&lt;/p&gt;
&lt;h2&gt;
  
  
  Inner Join
&lt;/h2&gt;

&lt;p&gt;Combines two tables by keeping only the rows that have matching values in a common 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%2Ftagd5vadhswlhb726uzv.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%2Ftagd5vadhswlhb726uzv.png" alt=" " width="799" height="152"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The Inner Join returned only records where CustomerID existed in both tables. Therefore, customers without orders and orders with invalid customer IDs were excluded.&lt;/p&gt;
&lt;h2&gt;
  
  
  Full Outer Join
&lt;/h2&gt;

&lt;p&gt;Combines two tables by keeping all rows from both tables, whether they match or not.&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%2F9kzv7so3ndlpq2bniv54.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%2F9kzv7so3ndlpq2bniv54.png" alt=" " width="799" height="183"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The Full Outer Join retained all records from both tables. This made it possible to see both types of mismatch: a customer with no order and an order whose customer does not exist in the Customers table.&lt;/p&gt;
&lt;h2&gt;
  
  
  Left Anti Join
&lt;/h2&gt;

&lt;p&gt;Returns only the rows from the first (left) table that do not have a match in the second (right) table.&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%2Feboj1emd7mtk3dcthjcx.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%2Feboj1emd7mtk3dcthjcx.png" alt=" " width="800" height="83"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The Left Anti Join returned records that existed in Customers but had no matching CustomerID in Orders. In this dataset, the result was C006, Faith Chebet. This type of join can be used to identify customers who have not placed an order.&lt;/p&gt;
&lt;h2&gt;
  
  
  Right Anti Join
&lt;/h2&gt;

&lt;p&gt;Returns only the rows from the right-hand (secondary) table that have no matching rows in the left-hand (primary) table.&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%2Fs3tq70zmtcoo0uhdmyv5.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%2Fs3tq70zmtcoo0uhdmyv5.png" alt=" " width="800" height="99"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The Right Anti Join returned records from Orders that had no corresponding CustomerID in Customers. The result identified O1008 with CustomerID C999, revealing a referential-integrity issue in the dataset.&lt;/p&gt;
&lt;h3&gt;
  
  
  Power Query Joins vs. Power BI Relationships
&lt;/h3&gt;

&lt;p&gt;A &lt;strong&gt;Power Query merge&lt;/strong&gt; and a &lt;strong&gt;Power BI relationship&lt;/strong&gt; both connect tables, but they work differently and are used at different stages of the Power BI workflow.&lt;/p&gt;
&lt;h4&gt;
  
  
  Does a Power Query Merge Physically Combine Data?
&lt;/h4&gt;

&lt;p&gt;&lt;strong&gt;Yes.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;A merge in Power Query combines columns from two tables into one table based on a matching key.&lt;/p&gt;

&lt;p&gt;For example, if we merge &lt;code&gt;Customers&lt;/code&gt; with &lt;code&gt;Orders&lt;/code&gt; using &lt;code&gt;CustomerID&lt;/code&gt;, customer information such as name and location can be added directly to the Orders table.&lt;/p&gt;

&lt;p&gt;This happens during the &lt;strong&gt;data preparation stage&lt;/strong&gt;, before the data is loaded into the Power BI data model.&lt;/p&gt;
&lt;h4&gt;
  
  
  Does Creating a Relationship Combine the Tables?
&lt;/h4&gt;

&lt;p&gt;&lt;strong&gt;No.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;A relationship keeps the tables separate.&lt;/p&gt;

&lt;p&gt;For example, &lt;code&gt;DimCustomer&lt;/code&gt; and &lt;code&gt;FactSales&lt;/code&gt; remain as two separate tables in the Power BI model.&lt;/p&gt;

&lt;p&gt;The relationship simply tells Power BI that the tables are connected through a common key.&lt;/p&gt;

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

&lt;p&gt;&lt;strong&gt;DimCustomer[CustomerID] → FactSales[CustomerID]&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Power BI can then use this relationship to connect customer information with sales when creating reports and calculations.&lt;/p&gt;
&lt;h4&gt;
  
  
  When Does Each Operation Happen?
&lt;/h4&gt;

&lt;p&gt;The two operations happen at different stages:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Merge:&lt;/strong&gt; Done in &lt;strong&gt;Power Query&lt;/strong&gt; during data preparation and transformation.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Relationship:&lt;/strong&gt; Created in the &lt;strong&gt;Power BI Data Model&lt;/strong&gt; after the tables have been loaded.
A simple way to remember this is:&lt;/li&gt;
&lt;/ul&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Power Query prepares and transforms the data, while the Data Model connects the tables for analysis.&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h4&gt;
  
  
  When Would You Choose a Merge Instead of a Relationship?
&lt;/h4&gt;

&lt;p&gt;A merge can be useful when you genuinely need to create &lt;strong&gt;one combined table&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;For example, you might need a single flat table for a specific export or for a system that does not support relationships.&lt;/p&gt;

&lt;p&gt;However, for most business intelligence reporting, especially when building a &lt;strong&gt;star schema&lt;/strong&gt;, relationships are generally preferred.&lt;/p&gt;
&lt;h4&gt;
  
  
  How Can Excessive Merging Affect a Data Model?
&lt;/h4&gt;

&lt;p&gt;Merging everything into large tables can reintroduce the &lt;strong&gt;redundancy&lt;/strong&gt; that a star schema is designed to avoid.&lt;/p&gt;

&lt;p&gt;For example, if customer information is merged into a sales table, the same customer name, location, and other details may be repeated across every transaction made by that customer.&lt;/p&gt;

&lt;p&gt;As the dataset grows, this can create wider and more difficult-to-maintain tables.&lt;/p&gt;

&lt;p&gt;It can also make the overall data model harder to understand because the clear separation between &lt;strong&gt;facts&lt;/strong&gt; and &lt;strong&gt;dimensions&lt;/strong&gt; is lost.&lt;/p&gt;
&lt;h4&gt;
  
  
  Why Keep Fact and Dimension Tables Separate?
&lt;/h4&gt;

&lt;p&gt;Keeping the tables separate has several advantages:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Less redundancy:&lt;/strong&gt; Customer information is stored once in &lt;code&gt;DimCustomer&lt;/code&gt; rather than repeated for every sale.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Cleaner data model:&lt;/strong&gt; Fact and dimension tables have clearly defined purposes.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Simpler analysis:&lt;/strong&gt; Measures can be created from the fact table while dimensions provide the context for analysis.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Easier maintenance:&lt;/strong&gt; If a customer's location changes, you update it in one place instead of updating many transaction rows.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Better scalability:&lt;/strong&gt; A well-designed star schema is easier to work with as the amount of data increases.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;
  
  
  A Practical Example
&lt;/h3&gt;

&lt;p&gt;Suppose Alice Wambui has made two orders:&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;CustomerID&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;101&lt;/td&gt;
&lt;td&gt;C001&lt;/td&gt;
&lt;td&gt;5,000&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;104&lt;/td&gt;
&lt;td&gt;C001&lt;/td&gt;
&lt;td&gt;8,000&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;In the &lt;code&gt;DimCustomer&lt;/code&gt; table, Alice's information is stored once:&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;Customer_Name&lt;/th&gt;
&lt;th&gt;Location&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;Alice Wambui&lt;/td&gt;
&lt;td&gt;Nairobi&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;If we &lt;strong&gt;merge&lt;/strong&gt; the customer table into the sales table, Alice's name and location would be repeated on both orders.&lt;/p&gt;

&lt;p&gt;If we instead create a &lt;strong&gt;relationship&lt;/strong&gt;, the customer information stays in &lt;code&gt;DimCustomer&lt;/code&gt;, while &lt;code&gt;FactSales&lt;/code&gt; only needs the &lt;code&gt;CustomerID&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                 FactSales

CustomerID                  CustomerID
Alice Wambui                C001
Nairobi                     C001
     |                          |
     └──────── 1 : * ──────────┘
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Power BI uses the relationship between &lt;code&gt;CustomerID&lt;/code&gt; and &lt;code&gt;CustomerID&lt;/code&gt; to connect the two tables when analyzing the data.&lt;/p&gt;

&lt;h3&gt;
  
  
  Key Takeaway
&lt;/h3&gt;

&lt;p&gt;The main difference is simple:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;A Power Query merge combines data into a table, while a Power BI relationship connects separate tables within the data model.&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;For a well-structured &lt;strong&gt;star schema&lt;/strong&gt;, keeping fact and dimension tables separate and connecting them with relationships is usually the better approach.&lt;/p&gt;

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

&lt;p&gt;For a typical business intelligence project, I would recommend using a &lt;strong&gt;star schema&lt;/strong&gt; as the preferred data model.&lt;/p&gt;

&lt;p&gt;A star schema provides a good balance between &lt;strong&gt;performance, simplicity, scalability, and maintainability&lt;/strong&gt;. It separates business transactions from descriptive information, making the model easier to understand and use when building Power BI reports.&lt;/p&gt;

&lt;p&gt;A typical model could look like this:&lt;br&gt;
&lt;/p&gt;

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

&lt;/div&gt;



&lt;p&gt;The &lt;strong&gt;FactSales&lt;/strong&gt; table would contain the business events and numerical values we want to analyze, such as sales amount, quantity, cost, and profit. The dimension tables would contain descriptive information such as product details, customer information, store details, and dates.&lt;/p&gt;

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

&lt;h4&gt;
  
  
  1. Better Query and Report Performance
&lt;/h4&gt;

&lt;p&gt;A star schema keeps dimension tables relatively small and the fact table focused on transactions and measures. This structure allows Power BI to efficiently filter and aggregate data when users interact with reports.&lt;/p&gt;

&lt;p&gt;Instead of storing customer or product information repeatedly in every sales transaction, the information is stored in the relevant dimension table and connected through relationships.&lt;/p&gt;

&lt;h4&gt;
  
  
  2. Simpler DAX
&lt;/h4&gt;

&lt;p&gt;Star schemas also make DAX easier to understand.&lt;br&gt;
For example, a measure can calculate total sales from the &lt;code&gt;FactSales&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;Total Sales = SUM(FactSales[Sales_Amount])
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Users can then analyze this measure by product category, store, customer type, or month using fields from the dimension tables.&lt;br&gt;
The relationships allow the filters from those dimensions to affect the fact table automatically.&lt;/p&gt;
&lt;h4&gt;
  
  
  3. Better Model Readability
&lt;/h4&gt;

&lt;p&gt;A star schema has a clear structure.&lt;br&gt;
The fact table sits at the center, while the dimension tables surround it. This makes it easier for another analyst to understand the model and identify where different types of information are stored.&lt;/p&gt;

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

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;FactSales&lt;/strong&gt; → What happened?&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;DimProduct&lt;/strong&gt; → What was sold?&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;DimCustomer&lt;/strong&gt; → Who bought it?&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;DimStore&lt;/strong&gt; → Where was it sold?&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;DimDate&lt;/strong&gt; → When did it happen?&lt;/li&gt;
&lt;/ul&gt;
&lt;h4&gt;
  
  
  4. Scalability
&lt;/h4&gt;

&lt;p&gt;A business may start with thousands of transactions and eventually grow to millions.&lt;br&gt;
Keeping fact and dimension tables separate provides a structure that can scale more easily as the amount of data increases.&lt;br&gt;
It also makes it easier to add new dimensions or attributes without completely restructuring the fact table.&lt;/p&gt;
&lt;h4&gt;
  
  
  5. Less Data Redundancy
&lt;/h4&gt;

&lt;p&gt;A star schema avoids unnecessarily repeating descriptive information.&lt;/p&gt;

&lt;p&gt;For example, instead of storing "Alice Wambui, Nairobi" on every transaction made by Alice, that information can be stored once in &lt;code&gt;DimCustomer&lt;/code&gt;.&lt;br&gt;
The fact table only needs the &lt;code&gt;CustomerID&lt;/code&gt; to identify the customer.This reduces redundancy and keeps the model organized.&lt;/p&gt;
&lt;h4&gt;
  
  
  6. Easier Maintenance
&lt;/h4&gt;

&lt;p&gt;If a customer's location changes, we can update the information in &lt;code&gt;DimCustomer&lt;/code&gt; rather than changing it across thousands of sales records.&lt;/p&gt;

&lt;p&gt;Similarly, if a product is moved to a different category, the relevant information can be maintained in &lt;code&gt;DimProduct&lt;/code&gt;.&lt;br&gt;
This makes the model easier to manage over time.&lt;/p&gt;
&lt;h2&gt;
  
  
  Recommended Relationships
&lt;/h2&gt;

&lt;p&gt;For a typical star schema, I would normally use &lt;strong&gt;one-to-many (1:*) relationships&lt;/strong&gt; between dimension tables and the fact table.&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;DimProduct[Product_Key]     1 ───── * FactSales[Product_Key]

DimCustomer[Customer_Key]   1 ───── * FactSales[Customer_Key]

DimStore[Store_Key]         1 ───── * FactSales[Store_Key]

DimDate[Date_Key]           1 ───── * FactSales[Date_Key]
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;strong&gt;dimension table is on the "one" side&lt;/strong&gt;, because each key should uniquely identify a record in that dimension.&lt;/p&gt;

&lt;p&gt;The &lt;strong&gt;fact table is on the "many" side&lt;/strong&gt;, because the same product, customer, store, or date can appear in many transactions.&lt;/p&gt;

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

&lt;p&gt;I would normally use &lt;strong&gt;single-direction filtering&lt;/strong&gt;, with the filter flowing from the dimension table to 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;DimProduct
    ↓
FactSales
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For example, if a user selects &lt;strong&gt;Shirts&lt;/strong&gt; from &lt;code&gt;DimProduct&lt;/code&gt;, the filter flows to &lt;code&gt;FactSales&lt;/code&gt; and Power BI calculates the sales for shirts.&lt;/p&gt;

&lt;p&gt;Single-direction filtering is generally preferable because it keeps the model's behavior predictable and reduces unnecessary complexity.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Bidirectional filtering&lt;/strong&gt; can be useful in specific scenarios, but I would not use it by default. Using it unnecessarily can create ambiguous filter paths and make a model more difficult to understand and troubleshoot.&lt;/p&gt;

&lt;h2&gt;
  
  
  What About a Flat Table or Snowflake Schema?
&lt;/h2&gt;

&lt;p&gt;A &lt;strong&gt;flat table&lt;/strong&gt; can be useful for simple datasets or small reporting tasks because everything is contained in one table. However, as the dataset grows, it can create a lot of repeated information and become difficult to maintain.&lt;/p&gt;

&lt;p&gt;A &lt;strong&gt;snowflake schema&lt;/strong&gt; normalizes dimensions into additional related tables. This can reduce redundancy, but it also introduces more relationships and complexity into the model.&lt;/p&gt;

&lt;p&gt;For most Power BI business intelligence projects, I would therefore choose a &lt;strong&gt;star schema&lt;/strong&gt; because it provides a practical middle ground:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Simple enough to understand, efficient enough to perform well, and flexible enough to scale.&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  Final Recommendation
&lt;/h3&gt;

&lt;p&gt;For a typical Power BI project, my preferred design would be:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Star schema&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;One central &lt;strong&gt;fact table&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;Multiple &lt;strong&gt;dimension tables&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;One-to-many relationships&lt;/strong&gt; from dimensions to the fact table&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Single-direction filter propagation&lt;/strong&gt; from dimensions to facts&lt;/li&gt;
&lt;li&gt;Clearly defined &lt;strong&gt;grain&lt;/strong&gt; for the fact table&lt;/li&gt;
&lt;li&gt;Measures stored and calculated from the fact table&lt;/li&gt;
&lt;li&gt;Descriptive attributes stored in dimension tables&lt;/li&gt;
&lt;li&gt;Avoid unnecessary merging of fact and dimension tables&lt;/li&gt;
&lt;/ul&gt;

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

&lt;p&gt;The goal is not simply to create relationships between tables, but to create a model that is &lt;strong&gt;easy to understand, efficient to query, simple to maintain, and flexible enough to support future reporting needs&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Ultimately, a good Power BI model should make it easier for both the developer and the end user to answer business questions without introducing unnecessary complexity.&lt;/p&gt;

</description>
      <category>powerbi</category>
      <category>powerquery</category>
      <category>datamodeling</category>
    </item>
    <item>
      <title>Jumia Product Analysis with Excel</title>
      <dc:creator>Mercy Siele</dc:creator>
      <pubDate>Sat, 05 Sep 2026 21:13:03 +0000</pubDate>
      <link>https://dev.to/siele/building-an-interactive-excel-dashboard-for-e-commerce-product-analysis-a-case-study-of-jumia-30o1</link>
      <guid>https://dev.to/siele/building-an-interactive-excel-dashboard-for-e-commerce-product-analysis-a-case-study-of-jumia-30o1</guid>
      <description>&lt;h2&gt;
  
  
  Introduction
&lt;/h2&gt;

&lt;p&gt;When shopping onine, I usually find myself looking at two things before making a purchase: Product ratings and reviews since I cannot examine the product physically.Products with high ratings and reviews tend to make the product trustworthy.This motivated me to explore how these factors using a sample dataset from Jumia.&lt;br&gt;
I analyzed a dataset of products listed to investigate whether pricing, discounts, ratings and review counts directly influence one another. By transforming this raw e-commerce data into an interactive Excel dashboard this project uncovers how strategic pricing directly impacts customer engagement.&lt;/p&gt;

&lt;h2&gt;
  
  
  Data Inspection
&lt;/h2&gt;

&lt;p&gt;Before data cleaning, I inspected all the data to find missing values, duplicates, inconsistent formats, and values that could affect the accuracy of the analysis.&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%2Fshr71x01uazlds6h55p8.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%2Fshr71x01uazlds6h55p8.png" alt=" " width="799" height="288"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Data Cleaning
&lt;/h2&gt;

&lt;p&gt;Before beginning the analysis, I cleaned and standardized the dataset to ensure that the values were accurate, consistent, and suitable for analysis in Excel. The main cleaning steps involved correcting data formats, handling missing values, and identifying duplicate records.&lt;/p&gt;

&lt;h3&gt;
  
  
  Correcting Data Formats
&lt;/h3&gt;

&lt;p&gt;I first reviewed each column and converted the values into appropriate data types.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Prices&lt;/strong&gt;- The price columns were initially stored as text because they included currency symbols (KSh), commas, and in some cases, price ranges such as &lt;code&gt;KSh 1,620 - KSh 1,980&lt;/code&gt;. I used Find and Replace (Ctrl + H) to remove the KSh text and other unnecessary characters then converted the values to numerical format. For products with price ranges, I calculated the average of the minimum and maximum prices and used this value for further analysis. I then recalculated the discount percentages based on the standardized prices.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Ratings&lt;/strong&gt;- Ratings were stored as text in formats such as 4 out of 5. I used Find and Replace (Ctrl + H) to remove out of 5 and converted the remaining values into numerical ratings.These were also formated as texts i.e &lt;code&gt;4 out of 5&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Review&lt;/strong&gt;- All the reviews counts were recorded as negative values. Since review counts represent the number of customer reviews and should be non-negative integers, I converted these values to their absolute values to ensure consistency.&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%2Fmuselfm28yh8fserk7vq.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%2Fmuselfm28yh8fserk7vq.png" alt=" " width="169" height="618"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Discounts&lt;/strong&gt;-  Discount values were standardized and formatted as percentages, allowing them to be compared consistently across products.&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Handling Missing Values&lt;/strong&gt;- The missing values were mostly from the Ratings and Reviews Columns which I replaced using &lt;code&gt;CTRL+H&lt;/code&gt; with &lt;code&gt;Nulls&lt;/code&gt;.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Duplicates&lt;/strong&gt;- To handle potential duplicates, I applied Conditional Formatting on the product column to highlight duplicate product names. I then manually cross-examined all columns across the flagged records to verify true duplicates:&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;-Three &lt;strong&gt;True Duplicates&lt;/strong&gt;: Confirmed as &lt;strong&gt;identical records&lt;/strong&gt; sharing exact matching product names, old prices, current prices, and discount percentages. These were permanently removed to prevent dataset skew.&lt;/p&gt;

&lt;p&gt;-Three &lt;strong&gt;Distinct Items&lt;/strong&gt;: Identified varied product configurations, as they exhibited different pricing structures and discount levels despite sharing similar names. These were retained in the dataset.&lt;br&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%2Fxd050rcxzrj9mnl70and.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%2Fxd050rcxzrj9mnl70and.png" alt=" " width="800" height="178"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Data Enrichment
&lt;/h2&gt;

&lt;p&gt;To enhance data analysis, I created four key derived columns in the dataset.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Rating Category.&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&amp;lt;/=3 – Poor&lt;/li&gt;
&lt;li&gt;&amp;gt;3 – 4.5 – Average&lt;/li&gt;
&lt;li&gt;&amp;gt;4.5 – 5 – Excellent&lt;/li&gt;
&lt;li&gt;Null – Not Provided&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Discount Category&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&amp;lt;/=20% – Low Discount&lt;/li&gt;
&lt;li&gt;&amp;gt;20% – 40%  Medium Discount&lt;/li&gt;
&lt;li&gt;&amp;gt;40% – High Discount&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Review Category.&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;1–23 – Low&lt;/li&gt;
&lt;li&gt;24–46 – Medium&lt;/li&gt;
&lt;li&gt;&amp;gt;46 – High&lt;/li&gt;
&lt;li&gt;Null – Not Provided&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Pricing Category.&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&amp;lt;/= KSh 1,000 – Low Price&lt;/li&gt;
&lt;li&gt;&amp;gt;KSh 1,000 – KSh 2,000  Medium Price&lt;/li&gt;
&lt;li&gt;&amp;gt;KSh 2,000 – High Price&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Absolute Discount&lt;/strong&gt;&lt;br&gt;
&lt;strong&gt;=D3-B3 = Old Price -Currect Price&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;I used the following IF functions to come up with the threshholds:&lt;br&gt;
-Reviews&lt;br&gt;
=IF(H2="null","Null",IF(H2&amp;gt;46,"Excellent",IF(H2&amp;gt;23,"Average","Poor")))&lt;br&gt;
-Ratings&lt;br&gt;
==IF(K2="null","Null",IF(K2&amp;gt;4.5,"Excellent",IF(K2&amp;gt;3,"Average","Poor")))&lt;br&gt;
-Price Category&lt;br&gt;
=IF(B2&amp;gt;2000,"High Price",IF(B2&amp;gt;1000,"Medium Price","Low Price"))&lt;br&gt;
-Discount&lt;br&gt;
=IF(F2&amp;gt;40%,"High Discount",IF(F2&amp;gt;20%,"Medium Discount","Low Discount"))&lt;/p&gt;

&lt;h2&gt;
  
  
  Data Analysis
&lt;/h2&gt;

&lt;p&gt;Using Pearson's Correlation Coefficient (r), I tested three core hypotheses regarding how pricing, discounts, ratings and reviews influence one another:&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%2Fgmi1t71mllcktv00cd39.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%2Fgmi1t71mllcktv00cd39.png" alt=" " width="799" height="348"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Key Insights&lt;/strong&gt;&lt;br&gt;
-As shown above,Pearson correlation analysis  of the discount and reviews (r = -0.131), confirming that offering deeper price cuts does not lead to higher customer engagement.&lt;br&gt;
-The correlation between rating and reviews is very weak (r = +0.057) because product rating has almost no impact on how many reviews a listing receives.This happens because Poor products and Moderate products generate almost the exact same average engagement.&lt;br&gt;
-The correlation between price and product ratings is very weak positive (r = +0.110) because price increases do not reliably drive higher customer ratings.&lt;/p&gt;

&lt;h2&gt;
  
  
  Dashboard Analysis
&lt;/h2&gt;

&lt;p&gt;I did the final analysis using a dashboard that had the following KPIs:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Total Products -112 products&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Average Current Price -KSh 1,187&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Average Discount-37%&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Average Rating -4/5&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Total Reviews -723 reviews&lt;/p&gt;&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%2Ffuucv1cgykmss6qlkcaj.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%2Ffuucv1cgykmss6qlkcaj.png" alt=" " width="799" height="168"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The Final Dashboard :&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%2Fdqfxtfvpr6h0q1e8gg31.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%2Fdqfxtfvpr6h0q1e8gg31.png" alt=" " width="800" height="565"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Key Recommendations
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Rethink the use of discounts to drive customer engagement up. Since the relationship between discounts and reviews was weak (r = -0.131), sellers should test different discount levels rather than assuming that larger discounts will generate more reviews.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Capitalize on &lt;strong&gt;"Medium Price"&lt;/strong&gt; products. Price and rating show a weak positive correlation (r = +0.110), with ratings rising in the Medium Price tier (4.1 rating) while High (3.88) and Low (3.78) lag behind.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Improve Products with High Engagement but Poor Ratings&lt;br&gt;
The analysis identified products that received a high number of reviews despite having poor ratings. For example, the 120W Cordless Vacuum Cleaner received 69 reviews but had a rating of 2.8. This suggests strong customer engagement but potentially low customer satisfaction.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

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

&lt;p&gt;This analysis provided insights into how ratings, reviews, prices, and discounts relate to product performance on Jumia. I initially assumed that larger discounts would lead to more reviews and stronger customer engagement. However, the weak relationship between discounts and reviews challenged this assumption. The analysis also showed that higher-priced products were not necessarily rated more highly, while products with many reviews were not always highly rated. These findings highlight the importance of considering product quality, customer satisfaction, and overall value, rather than relying on price, discounts or reviews alone.&lt;/p&gt;

</description>
      <category>analytics</category>
      <category>beginners</category>
      <category>microsoft</category>
    </item>
    <item>
      <title>Getting Started with Excel for Data Analytics: From Basics to Data Cleaning</title>
      <dc:creator>Mercy Siele</dc:creator>
      <pubDate>Sun, 30 Aug 2026 14:33:13 +0000</pubDate>
      <link>https://dev.to/siele/getting-started-with-excel-for-data-analytics-from-basics-to-data-cleaning-41fl</link>
      <guid>https://dev.to/siele/getting-started-with-excel-for-data-analytics-from-basics-to-data-cleaning-41fl</guid>
      <description>&lt;h2&gt;
  
  
  Introduction
&lt;/h2&gt;

&lt;p&gt;Microsoft Excel is one of the most powerful and beginner friendly tool for data analytics. It helps you to organize,store, calculate, clean,analyze and visalize data. &lt;br&gt;
Data analytics is the process of collecting, cleaning and examinig raw data to find useful insights.&lt;/p&gt;

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

&lt;p&gt;Before diving into the specific data cleaning techniques I learnt, we can get into the basics of Excel to avoid feeling overwhelmed by all the gridlines.&lt;/p&gt;

&lt;h3&gt;
  
  
  Excel ribbon
&lt;/h3&gt;

&lt;p&gt;This are tabs at the top of Excel interface that helps users to navigate and locate commands when using Excel.&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%2F52cxpsdgcj8esd47279h.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%2F52cxpsdgcj8esd47279h.png" alt=" " width="800" height="128"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The following are the core tabs under the Excel ribbons and the use of each:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;1. Home tab&lt;/strong&gt;-this is the everyday button that contains different formating options such, font type (colour and size), alignment options,number formating (Currency,date, accounting,date and time),styles, cells and editing.&lt;br&gt;
&lt;strong&gt;2. Insert&lt;/strong&gt;- This is used to insert charts, pivot tables, symbols, tables,shapes, slicers, comments and links.&lt;br&gt;
&lt;strong&gt;3.Page Layout&lt;/strong&gt;- It contains a list of commands that determine how the pay appears.&lt;br&gt;
&lt;strong&gt;4.Formulas&lt;/strong&gt;- It contains mathematical formulars used for calculation and formula auditing tools.&lt;br&gt;
&lt;strong&gt;5.Data&lt;/strong&gt;- This is a section used ti import data, sort, filter and analyse data. &lt;br&gt;
&lt;strong&gt;6.Review&lt;/strong&gt;-Has tools used to check spelling, manage accessibility and to check the performance of the workbook.&lt;/p&gt;

&lt;h2&gt;
  
  
  Excel Terms
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Cell&lt;/strong&gt;- This is a single box in which column and rows meet.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Column&lt;/strong&gt;- This is a vertical group of cells labelled with letters (A,B,C).&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Row&lt;/strong&gt;-A horizontal group of cells labelled with numbers (1,2,3)&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Worksheet&lt;/strong&gt;- A single sheet inside Excel used to enter data.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Workbook&lt;/strong&gt;-A file in excel containing one or more worksheets.&lt;/p&gt;&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%2Feoxp3nn16rlp37dyf2a0.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%2Feoxp3nn16rlp37dyf2a0.png" alt=" " width="800" height="533"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Data Cleaning in Excel
&lt;/h2&gt;

&lt;p&gt;Data cleaning is the processs of identifying and correcting errors, inconsistencies, duplicates and missing values to ensre it is accurate, reliable and ready for analysis.&lt;/p&gt;

&lt;h3&gt;
  
  
  Steps Taken During Data Cleaning :
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;1. I inspected the dataset I was checking on to check for the any issues.&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%2Fd4yv9kz5mdqwlb7lq5uw.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%2Fd4yv9kz5mdqwlb7lq5uw.png" alt=" " width="800" height="409"&gt;&lt;/a&gt;&lt;br&gt;
Below are the issues I ecountered:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Missing values&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Duplicates&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Inconsistent formating&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;2.Removing duplicates&lt;/strong&gt;&lt;br&gt;
There were data that appeared more than once as shown.&lt;br&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%2F0ievv4d7lmd17j45zbu8.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%2F0ievv4d7lmd17j45zbu8.png" alt=" " width="800" height="25"&gt;&lt;/a&gt;&lt;br&gt;
To identify and remove duplicates, I used the &lt;strong&gt;Employee ID&lt;/strong&gt; as the unique identifier since each employee should have a different ID.&lt;br&gt;
I selected the dataset,under data tab on the ribbon, I selected remove duplicates under data tools. I selected the Employee ID column to allow Excel to identify duplicates and remove them.&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%2Fcr88oti53qdc0ti87ny4.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%2Fcr88oti53qdc0ti87ny4.png" alt=" " width="800" height="334"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;3.Removing Missing Values&lt;/strong&gt;&lt;br&gt;
The missing values appeared on most of my column and I replaced the text types by using "Unknown" value.&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%2Fgsjphcvxfxcl3gbzwbvl.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%2Fgsjphcvxfxcl3gbzwbvl.png" alt=" " width="800" height="184"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;4.Correcting Inconsistent Text&lt;/strong&gt;&lt;br&gt;
The same information was written in the same format.Some with different capitalization, extra spaces and spelling variation. Which will cause inconsistencies during analysis.&lt;br&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%2Fccrc5ozu6ycblf98560z.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%2Fccrc5ozu6ycblf98560z.png" alt=" " width="800" height="87"&gt;&lt;/a&gt;&lt;br&gt;
To correct this ,I used find and replace: &lt;code&gt;Find what&amp;gt; Replace With&amp;gt; Replace All&lt;/code&gt;.&lt;br&gt;
This ensured that all the data remained consistent.&lt;br&gt;
I also used Excel text functions to standardize the data:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;code&gt;TRIM&lt;/code&gt;-removes extra spaces from texts.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;code&gt;PROPER&lt;/code&gt;-changes the texts to proper case.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;code&gt;UPPER&lt;/code&gt;-changes the texts to upper case.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;code&gt;LOWER&lt;/code&gt;-changes the texts to lower case.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;5.Vaidating Cleaned Data&lt;/strong&gt;&lt;br&gt;
After completing the data cleaning process, I validated the data to ensure the changes had been made.&lt;br&gt;
I used &lt;strong&gt;Filters&lt;/strong&gt; and &lt;strong&gt;Sorting&lt;/strong&gt; to check for any incorrect and unusal values. I also used &lt;strong&gt;Conditional Formating&lt;/strong&gt; to check for any available duplicate values and missing values.&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%2F3kfyf9ysexpt2twegm73.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%2F3kfyf9ysexpt2twegm73.png" alt=" " width="800" height="230"&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%2Fccrqk09yjkgl1yqegc0g.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%2Fccrqk09yjkgl1yqegc0g.png" alt=" " width="577" height="436"&gt;&lt;/a&gt;&lt;br&gt;
I also checked for the data types to ensure that columns were being stored under the correct data types such as dates under dates,numerical values as numbers.&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%2Fouuqc14rrkh13hzabuck.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%2Fouuqc14rrkh13hzabuck.png" alt=" " width="800" height="641"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;After doing this, I confired that the data was ready for the next step which is analysis.&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%2Fr6klx5apvd6pqk9xwohn.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%2Fr6klx5apvd6pqk9xwohn.png" alt=" " width="800" height="273"&gt;&lt;/a&gt;&lt;/p&gt;

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

&lt;p&gt;Through the entire process, I realised that data cleaning is not just about deleting blanks, removing blanks and correcting obvious errors. It is about understanding the data, investigating inconsistencies and making informed decisions before analysis. Every missing value, duplicate, unusual value can tell a story and removing it can lead to misleading insights.&lt;/p&gt;

</description>
      <category>beginners</category>
      <category>data</category>
      <category>microsoft</category>
      <category>excel</category>
    </item>
    <item>
      <title>My First Github Project:From a Local Folder to GitHub Using Git and SSH</title>
      <dc:creator>Mercy Siele</dc:creator>
      <pubDate>Sun, 23 Aug 2026 04:48:36 +0000</pubDate>
      <link>https://dev.to/siele/my-first-github-projectfrom-a-local-folder-to-github-using-git-and-ssh-79g</link>
      <guid>https://dev.to/siele/my-first-github-projectfrom-a-local-folder-to-github-using-git-and-ssh-79g</guid>
      <description>&lt;h2&gt;
  
  
  Introdction
&lt;/h2&gt;

&lt;p&gt;I always knew that GitHub is where developers keep and share their projects but I have always wondered how developers get the projects from their computers onto GitHub.When I started learning Git aeverything started falling into place. I recently worked on my first GitHub project using Git, Git bash, GitHub and SSH key.&lt;br&gt;
Before I explain what I did, I first wanted to understand what each of these terms mean.&lt;/p&gt;

&lt;h2&gt;
  
  
  Key Terms
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;- Git&lt;/strong&gt;&lt;br&gt;
It is a software tool that tracks changes locally in your computer.&lt;br&gt;
&lt;strong&gt;- GitHub&lt;/strong&gt;&lt;br&gt;
It is an online platform where repositories are posted online for collaboration.&lt;br&gt;
&lt;strong&gt;- SSH Key&lt;/strong&gt;&lt;br&gt;
This is a secure digital credential that is used to connect the local Git software to the online GitHub account without typing a password everytime.&lt;br&gt;
&lt;strong&gt;- Git Bash&lt;/strong&gt;&lt;br&gt;
This is a program used to interact with Git using commands&lt;/p&gt;

&lt;h2&gt;
  
  
  Setting up my Project Folder
&lt;/h2&gt;

&lt;p&gt;I started by creating a folder for my project on the Computer.My project was called Kenya-Hospitals-Records-Analysis.&lt;/p&gt;

&lt;h3&gt;
  
  
  Creating Project Folder
&lt;/h3&gt;

&lt;p&gt;I used &lt;code&gt;mkdir&lt;/code&gt;command to create the folder.This is a command used to create a new folder.&lt;br&gt;
&lt;code&gt;mkdir Kenya-Hospitals-Records-Analysis&lt;/code&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Moving into the Project Folder.
&lt;/h3&gt;

&lt;p&gt;I used the &lt;code&gt;cd&lt;/code&gt; command to move to the project folder. &lt;br&gt;
The &lt;code&gt;cd Kenya-Hospitals-Records-Analysis&lt;/code&gt; allowed me to move into the folder itself.&lt;/p&gt;

&lt;h3&gt;
  
  
  Creating Files and Folders
&lt;/h3&gt;

&lt;p&gt;I created another folder inside Kenya-Hospitals-Records-Analysis using command:&lt;br&gt;
 &lt;code&gt;mkdir Data&lt;/code&gt; &lt;br&gt;
and a file  using command:&lt;br&gt;
 &lt;code&gt;touch README.md&lt;/code&gt;&lt;br&gt;
The &lt;code&gt;touch&lt;/code&gt;command is used to create a new file.&lt;br&gt;
I used &lt;code&gt;README.md&lt;/code&gt; file to document my project.&lt;br&gt;
The &lt;code&gt;Data&lt;/code&gt;folder contained my project data.&lt;/p&gt;

&lt;h3&gt;
  
  
  Checking Folders
&lt;/h3&gt;

&lt;p&gt;After creating folders and files, I used &lt;code&gt;ls&lt;/code&gt;command to check what was inside my project folder. The command listed Data folder and README.md file confirming that they had been successfully created.&lt;/p&gt;

&lt;h2&gt;
  
  
  Initializing Git
&lt;/h2&gt;

&lt;p&gt;I initialized Git using &lt;code&gt;git init&lt;/code&gt; command. This created a hidden folder inside my project folder which initialized a new Git repository.&lt;/p&gt;

&lt;h2&gt;
  
  
  Stagging and Commiting Files
&lt;/h2&gt;

&lt;p&gt;After initializing my project as a Git repository, I first checked the status of my project to know the files that were not being tracked using &lt;code&gt;git status&lt;/code&gt;.&lt;br&gt;
I then used &lt;code&gt;git add .&lt;/code&gt;command to add all the files in my project to the staging area for commit.&lt;br&gt;
After staging my files, I used :&lt;br&gt;
&lt;code&gt;git commit -m&lt;/code&gt;"First commit" . The &lt;code&gt;git commit -m&lt;/code&gt; command saves the staged changes in my local git repository.&lt;br&gt;
 I used "First commit" as the commit message because it was the first time saving the project as a git. &lt;/p&gt;

&lt;h2&gt;
  
  
  Connecting My local Repository to GitHub
&lt;/h2&gt;

&lt;p&gt;After making the first commit, I used &lt;code&gt;git branch&lt;/code&gt; to check on the branch I was working on. The branch I was working on was the main branch.&lt;br&gt;
Before starting the project, I had already set up an SSH key and added the public key to my GitHub. The SSH key allowed my computer to securely authenticate with Github when working with my remote repository.&lt;br&gt;
I then connected my local repository with the GitHb repository using the command &lt;code&gt;git remote add origin git@github.com:username/Kenya-Hospitals-Records-Analysis.git&lt;/code&gt;.&lt;br&gt;
The origin being the name given to GitHub remote repository.&lt;br&gt;
I then checked the connection using &lt;code&gt;git remote -v&lt;/code&gt; which displayed the GitHub repository where my local repository was connected to. Once the connection was done, I used the command &lt;code&gt;git push -u origin main&lt;/code&gt;. This command pushes the committed changes on my local repository to GitHub.&lt;/p&gt;

&lt;h2&gt;
  
  
  Verifying the Project on GitHub
&lt;/h2&gt;

&lt;p&gt;Finally, I opened my GitHub to verify that my project had been uploaded.The &lt;code&gt;Data&lt;/code&gt; and &lt;code&gt;README.md&lt;/code&gt;were displayed verifying that I had successfuly uploaded my project.&lt;/p&gt;

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

&lt;p&gt;This practical experience gave me the foundation on how to use Git,GitHub,Git bash and SSH key.I learnt how to create a local project,initialize it as a local repository,commit changes,connect it to GitHub and push the project to GitHub using SSH key.What seemed like complicated process became easier to understand once I learnt the use of the commands and the different stages in the process.&lt;/p&gt;

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