<?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: Victoria Ndei</title>
    <description>The latest articles on DEV Community by Victoria Ndei (@victoria_ndei_07224d0b8db).</description>
    <link>https://dev.to/victoria_ndei_07224d0b8db</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%2F4071801%2Ffa0c0c1c-dcde-437a-b875-bc329aff08df.png</url>
      <title>DEV Community: Victoria Ndei</title>
      <link>https://dev.to/victoria_ndei_07224d0b8db</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/victoria_ndei_07224d0b8db"/>
    <language>en</language>
    <item>
      <title># SQL Essentials: Joins, Window Functions, CTEs, and Subqueries Using a Hospital Database</title>
      <dc:creator>Victoria Ndei</dc:creator>
      <pubDate>Sun, 18 Oct 2026 05:39:00 +0000</pubDate>
      <link>https://dev.to/victoria_ndei_07224d0b8db/-sql-essentials-joins-window-functions-ctes-and-subqueries-using-a-hospital-database-2n39</link>
      <guid>https://dev.to/victoria_ndei_07224d0b8db/-sql-essentials-joins-window-functions-ctes-and-subqueries-using-a-hospital-database-2n39</guid>
      <description>&lt;h2&gt;
  
  
  Introduction
&lt;/h2&gt;

&lt;p&gt;SQL is more than retrieving data from a database. It also helps us combine information from multiple tables, analyse trends, rank records, calculate running totals, and break down complex queries into manageable steps.&lt;/p&gt;

&lt;p&gt;In this article, we will explore four essential SQL concepts: &lt;strong&gt;joins, window functions, Common Table Expressions (CTEs), and subqueries&lt;/strong&gt;. Using a hospital database implemented in PostgreSQL, we will examine how each concept works, when to use it, and how it can help answer practical questions.&lt;/p&gt;

&lt;p&gt;The hospital dataset contains information about patients, doctors, departments, diagnoses, procedures, wards, and hospital visits. It allows us to explore questions such as which department has the highest billing, which patients have multiple visits, and how individual bills compare with group averages.&lt;/p&gt;

&lt;h2&gt;
  
  
  Target audience
&lt;/h2&gt;

&lt;p&gt;This article is intended for:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Beginners learning SQL and relational databases.&lt;/li&gt;
&lt;li&gt;Students completing database and data analytics assignments.&lt;/li&gt;
&lt;li&gt;Aspiring data analysts who want practical SQL experience.&lt;/li&gt;
&lt;li&gt;Developers who want to write clearer and more effective SQL queries.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Prerequisites
&lt;/h2&gt;

&lt;p&gt;Before following the examples, you should have:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Basic knowledge of SQL statements such as &lt;code&gt;SELECT&lt;/code&gt;, &lt;code&gt;FROM&lt;/code&gt;, &lt;code&gt;WHERE&lt;/code&gt;, and &lt;code&gt;ORDER BY&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;A basic understanding of relational database tables, primary keys, and foreign keys.&lt;/li&gt;
&lt;li&gt;Access to PostgreSQL or a PostgreSQL-compatible environment.&lt;/li&gt;
&lt;li&gt;The hospital database script used in this article.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Learning objectives
&lt;/h2&gt;

&lt;p&gt;By the end of this article, you should be able to:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Explain and use different types of SQL joins.&lt;/li&gt;
&lt;li&gt;Apply window functions to rank records, compare visits, and calculate aggregates.&lt;/li&gt;
&lt;li&gt;Understand &lt;code&gt;OVER()&lt;/code&gt;, &lt;code&gt;PARTITION BY&lt;/code&gt;, window ordering, and window frames.&lt;/li&gt;
&lt;li&gt;Create basic, multiple, and recursive CTEs.&lt;/li&gt;
&lt;li&gt;Use scalar, correlated, non-correlated, and nested subqueries.&lt;/li&gt;
&lt;li&gt;Apply &lt;code&gt;IN&lt;/code&gt;, &lt;code&gt;EXISTS&lt;/code&gt;, &lt;code&gt;ANY&lt;/code&gt;, and &lt;code&gt;ALL&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Decide when to use a CTE, join, window function, or subquery.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Understanding the hospital database
&lt;/h2&gt;

&lt;p&gt;The database uses the PostgreSQL schema &lt;code&gt;morning&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;The main tables include:&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;Description&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;morning.patients&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Stores patient names, ages, genders, counties, and sub-counties.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;morning.doctor&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Contains doctor names, specialties, genders, and experience.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;morning.department&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Stores hospital department names and IDs.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;morning.diagnosis&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Contains diagnosis names and categories.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;morning.procedure&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Stores medical procedure names and categories.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;morning.words&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Stores ward IDs and ward names.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;morning.visit_flat&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Contains visit dates, patient and doctor IDs, diagnoses, procedures, admission details, bills, and satisfaction scores.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The dataset contains 30 hospital visits, 25 patients, four doctors, four departments, 18 diagnoses, 15 procedures, and four wards.&lt;/p&gt;

&lt;p&gt;Most examples use &lt;code&gt;morning.visit_flat&lt;/code&gt; because it contains the records for individual hospital visits.&lt;/p&gt;

&lt;p&gt;One important implementation detail is that the supplied script names the ward table &lt;code&gt;morning. wards&lt;/code&gt;. The examples retain that exact table name. Also, the script does not declare foreign-key constraints for &lt;code&gt;doctor_id&lt;/code&gt; and &lt;code&gt;department_id&lt;/code&gt;, although the IDs match the corresponding records in the supplied data.&lt;/p&gt;




&lt;h2&gt;
  
  
  1. SQL Joins: Combining Data from Multiple Tables
&lt;/h2&gt;

&lt;p&gt;A database often stores related information in separate tables to reduce duplication and improve organization. Joins allow us to retrieve related information using matching columns.&lt;/p&gt;

&lt;p&gt;For example, the patient's name is stored in &lt;code&gt;patients&lt;/code&gt;, while the visit date and bill are stored in &lt;code&gt;visit_flat&lt;/code&gt;. A join allows us to display this information together.&lt;/p&gt;

&lt;h2&gt;
  
  
  INNER JOIN
&lt;/h2&gt;

&lt;p&gt;An &lt;code&gt;INNER JOIN&lt;/code&gt; returns only records that have matching values in both tables.&lt;/p&gt;

&lt;h3&gt;
  
  
  Example
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt;
    &lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;patient_name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;v&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;visit_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;v&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;visit_date&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;v&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;total_bill_kes&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;morning&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;patients&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;p&lt;/span&gt;
&lt;span class="k"&gt;INNER&lt;/span&gt; &lt;span class="k"&gt;JOIN&lt;/span&gt; &lt;span class="n"&gt;morning&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;visit_flat&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;v&lt;/span&gt;
    &lt;span class="k"&gt;ON&lt;/span&gt; &lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;patient_id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;v&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;patient_id&lt;/span&gt;
&lt;span class="k"&gt;ORDER&lt;/span&gt; &lt;span class="k"&gt;BY&lt;/span&gt; &lt;span class="n"&gt;v&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;visit_id&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Explanation
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;patients&lt;/code&gt; provides the patient's name.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;visit_flat&lt;/code&gt; provides the visit ID, date, and bill.&lt;/li&gt;
&lt;li&gt;The &lt;code&gt;ON&lt;/code&gt; clause matches records using &lt;code&gt;patient_id&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Expected result
&lt;/h3&gt;

&lt;p&gt;The query returns 30 rows because all 30 visits have matching patient records.&lt;/p&gt;

&lt;p&gt;For example, visit &lt;code&gt;1001&lt;/code&gt; belongs to Amina Otieno, occurred on 1 June 2026, and has a recorded bill of KSh 3,500.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;When should you use INNER JOIN?&lt;/strong&gt; Use it when you only want records with matching data in both tables.&lt;/p&gt;

&lt;h2&gt;
  
  
  LEFT JOIN
&lt;/h2&gt;

&lt;p&gt;A &lt;code&gt;LEFT JOIN&lt;/code&gt; returns every record from the left table and matching records from the right table. If no match exists, the right-side columns contain &lt;code&gt;NULL&lt;/code&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Example
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt;
    &lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;patient_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;patient_name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;v&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;visit_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;v&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;visit_date&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;morning&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;patients&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;p&lt;/span&gt;
&lt;span class="k"&gt;LEFT&lt;/span&gt; &lt;span class="k"&gt;JOIN&lt;/span&gt; &lt;span class="n"&gt;morning&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;visit_flat&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;v&lt;/span&gt;
    &lt;span class="k"&gt;ON&lt;/span&gt; &lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;patient_id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;v&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;patient_id&lt;/span&gt;
&lt;span class="k"&gt;ORDER&lt;/span&gt; &lt;span class="k"&gt;BY&lt;/span&gt; &lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;patient_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;v&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;visit_date&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Explanation
&lt;/h3&gt;

&lt;p&gt;Every patient is retained, even if they have no visit record. Patients with multiple visits appear more than once.&lt;/p&gt;

&lt;p&gt;In the supplied dataset, every patient has at least one recorded visit, so no patient has &lt;code&gt;NULL&lt;/code&gt; visit details.&lt;/p&gt;

&lt;p&gt;To find patients without recorded visits, we can use:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt;
    &lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;patient_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;patient_name&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;morning&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;patients&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;p&lt;/span&gt;
&lt;span class="k"&gt;LEFT&lt;/span&gt; &lt;span class="k"&gt;JOIN&lt;/span&gt; &lt;span class="n"&gt;morning&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;visit_flat&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;v&lt;/span&gt;
    &lt;span class="k"&gt;ON&lt;/span&gt; &lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;patient_id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;v&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;patient_id&lt;/span&gt;
&lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;v&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;visit_id&lt;/span&gt; &lt;span class="k"&gt;IS&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This returns zero rows for the current dataset.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;When should you use LEFT JOIN?&lt;/strong&gt; Use it when all records from the left-hand table must be retained, including those without matching records on the right.&lt;/p&gt;

&lt;h2&gt;
  
  
  RIGHT JOIN
&lt;/h2&gt;

&lt;p&gt;A &lt;code&gt;RIGHT JOIN&lt;/code&gt; returns every record from the right table and matching records from the left table.&lt;/p&gt;

&lt;h3&gt;
  
  
  Example
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt;
    &lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;patient_name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;v&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;visit_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;v&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;visit_date&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;v&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;total_bill_kes&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;morning&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;patients&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;p&lt;/span&gt;
&lt;span class="k"&gt;RIGHT&lt;/span&gt; &lt;span class="k"&gt;JOIN&lt;/span&gt; &lt;span class="n"&gt;morning&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;visit_flat&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;v&lt;/span&gt;
    &lt;span class="k"&gt;ON&lt;/span&gt; &lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;patient_id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;v&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;patient_id&lt;/span&gt;
&lt;span class="k"&gt;ORDER&lt;/span&gt; &lt;span class="k"&gt;BY&lt;/span&gt; &lt;span class="n"&gt;v&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;visit_id&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Explanation
&lt;/h3&gt;

&lt;p&gt;Every visit is retained. If a visit referred to a patient who did not exist in the patients table, the patient columns would contain &lt;code&gt;NULL&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;In the supplied dataset, all visit patient IDs have matching patient records, so all 30 visits display a patient name.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;When should you use RIGHT JOIN?&lt;/strong&gt; Use it when every record from the right-hand table must be retained. The same result can often be expressed with a &lt;code&gt;LEFT JOIN&lt;/code&gt; by reversing the table order.&lt;/p&gt;

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

&lt;p&gt;A &lt;code&gt;FULL OUTER JOIN&lt;/code&gt; returns all matching records and preserves unmatched records from both tables.&lt;/p&gt;

&lt;h3&gt;
  
  
  Example
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt;
    &lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;patient_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;patient_name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;v&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;visit_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;v&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;total_bill_kes&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;morning&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;patients&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;p&lt;/span&gt;
&lt;span class="k"&gt;FULL&lt;/span&gt; &lt;span class="k"&gt;OUTER&lt;/span&gt; &lt;span class="k"&gt;JOIN&lt;/span&gt; &lt;span class="n"&gt;morningvisit_flat&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;v&lt;/span&gt;
    &lt;span class="k"&gt;ON&lt;/span&gt; &lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;patient_id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;v&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;patient_id&lt;/span&gt;
&lt;span class="k"&gt;ORDER&lt;/span&gt; &lt;span class="k"&gt;BY&lt;/span&gt; &lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;patient_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;v&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;visit_id&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Explanation
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Matching patient and visit records are combined.&lt;/li&gt;
&lt;li&gt;Patients without visits would still appear, with visit columns set to &lt;code&gt;NULL&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Visits without matching patients would also appear, with patient columns set to &lt;code&gt;NULL&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The supplied dataset has no unmatched patient-to-visit records, so the query returns 30 rows.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;When should you use FULL OUTER JOIN?&lt;/strong&gt; Use it when comparing or reconciling datasets where either side may contain unmatched records.&lt;/p&gt;

&lt;h2&gt;
  
  
  CROSS JOIN
&lt;/h2&gt;

&lt;p&gt;A &lt;code&gt;CROSS JOIN&lt;/code&gt; returns every possible combination of rows from two tables.&lt;/p&gt;

&lt;h3&gt;
  
  
  Example
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt;
    &lt;span class="n"&gt;d&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;doctor_name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;dep&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;department_name&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;morning&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;doctor&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;d&lt;/span&gt;
&lt;span class="k"&gt;CROSS&lt;/span&gt; &lt;span class="k"&gt;JOIN&lt;/span&gt; &lt;span class="n"&gt;morning&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt; &lt;span class="n"&gt;department&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;dep&lt;/span&gt;
&lt;span class="k"&gt;ORDER&lt;/span&gt; &lt;span class="k"&gt;BY&lt;/span&gt; &lt;span class="n"&gt;d&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;doctor_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;dep&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;department_id&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Explanation
&lt;/h3&gt;

&lt;p&gt;The dataset contains four doctors and four departments. The query generates every possible doctor-department combination.&lt;/p&gt;

&lt;p&gt;The expected number of rows is:&lt;/p&gt;

&lt;p&gt;4 doctors × 4 departments = 16 combinations.&lt;/p&gt;

&lt;p&gt;These combinations are generated for demonstration; they do not represent confirmed doctor assignments.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;When should you use CROSS JOIN?&lt;/strong&gt; Use it when you need every possible combination, such as generating combinations for testing or analysis.&lt;/p&gt;

&lt;h2&gt;
  
  
  SELF JOIN
&lt;/h2&gt;

&lt;p&gt;A self join joins a table to itself. It allows us to compare records within the same table.&lt;/p&gt;

&lt;h3&gt;
  
  
  Example: Find patients from the same county
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt;
    &lt;span class="n"&gt;p1&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;patient_name&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;patient_one&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;p2&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;patient_name&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;patient_two&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;p1&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;county&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;morning&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;patients&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;p1&lt;/span&gt;
&lt;span class="k"&gt;INNER&lt;/span&gt; &lt;span class="k"&gt;JOIN&lt;/span&gt; &lt;span class="n"&gt;morning&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;patients&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;p2&lt;/span&gt;
    &lt;span class="k"&gt;ON&lt;/span&gt; &lt;span class="n"&gt;p1&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;county&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;p2&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;county&lt;/span&gt;
   &lt;span class="k"&gt;AND&lt;/span&gt; &lt;span class="n"&gt;p1&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;patient_id&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="n"&gt;p2&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;patient_id&lt;/span&gt;
&lt;span class="k"&gt;ORDER&lt;/span&gt; &lt;span class="k"&gt;BY&lt;/span&gt; &lt;span class="n"&gt;p1&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;county&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;p1&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;patient_id&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Explanation
&lt;/h3&gt;

&lt;p&gt;The aliases &lt;code&gt;p1&lt;/code&gt; and &lt;code&gt;p2&lt;/code&gt; represent two instances of the same table.&lt;/p&gt;

&lt;p&gt;The query matches patients from the same county. The condition &lt;code&gt;p1.patient_id &amp;lt; p2.patient_id&lt;/code&gt; prevents a patient from being matched with themselves and avoids returning the same pair twice.&lt;/p&gt;

&lt;p&gt;For example, Amina Otieno and Isaac Kamau both come from Nairobi, while Esther Achieng and Brian Omondi both come from Kisumu.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;When should you use SELF JOIN?&lt;/strong&gt; Use it when comparing records within one table, such as patients from the same county or employees who share a manager.&lt;/p&gt;

&lt;h2&gt;
  
  
  Joining multiple hospital tables
&lt;/h2&gt;

&lt;p&gt;Joins can combine several tables to create a more complete hospital visit report.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt;
    &lt;span class="n"&gt;v&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;visit_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;v&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;visit_date&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;patient_name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;d&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;doctor_name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;dep&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;department_name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;dg&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;diagnosis_name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;pr&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;procedure_name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;w&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ward_name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;v&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;total_bill_kes&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;morning&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;visit_flat&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;v&lt;/span&gt;
&lt;span class="k"&gt;INNER&lt;/span&gt; &lt;span class="k"&gt;JOIN&lt;/span&gt; &lt;span class="n"&gt;morning&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;patients&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;p&lt;/span&gt;
    &lt;span class="k"&gt;ON&lt;/span&gt; &lt;span class="n"&gt;v&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;patient_id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;patient_id&lt;/span&gt;
&lt;span class="k"&gt;INNER&lt;/span&gt; &lt;span class="k"&gt;JOIN&lt;/span&gt; &lt;span class="n"&gt;morning&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;doctor&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;d&lt;/span&gt;
    &lt;span class="k"&gt;ON&lt;/span&gt; &lt;span class="n"&gt;v&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;doctor_id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;d&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;doctor_id&lt;/span&gt;
&lt;span class="k"&gt;INNER&lt;/span&gt; &lt;span class="k"&gt;JOIN&lt;/span&gt; &lt;span class="n"&gt;morning&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;department&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;dep&lt;/span&gt;
    &lt;span class="k"&gt;ON&lt;/span&gt; &lt;span class="n"&gt;v&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;department_id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;dep&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;department_id&lt;/span&gt;
&lt;span class="k"&gt;INNER&lt;/span&gt; &lt;span class="k"&gt;JOIN&lt;/span&gt; &lt;span class="n"&gt;morning&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;diagnosis&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;dg&lt;/span&gt;
    &lt;span class="k"&gt;ON&lt;/span&gt; &lt;span class="n"&gt;v&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;diagnosis_id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;dg&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;diagnosis_id&lt;/span&gt;
&lt;span class="k"&gt;INNER&lt;/span&gt; &lt;span class="k"&gt;JOIN&lt;/span&gt; &lt;span class="n"&gt;morning&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;procedure&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;pr&lt;/span&gt;
    &lt;span class="k"&gt;ON&lt;/span&gt; &lt;span class="n"&gt;v&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;procedure_id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;pr&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;procedure_id&lt;/span&gt;
&lt;span class="k"&gt;INNER&lt;/span&gt; &lt;span class="k"&gt;JOIN&lt;/span&gt; &lt;span class="n"&gt;morning&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;words&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;w&lt;/span&gt;
    &lt;span class="k"&gt;ON&lt;/span&gt; &lt;span class="n"&gt;v&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ward_id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;w&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ward_id&lt;/span&gt;
&lt;span class="k"&gt;ORDER&lt;/span&gt; &lt;span class="k"&gt;BY&lt;/span&gt; &lt;span class="n"&gt;v&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;visit_date&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;v&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;visit_id&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This query combines visit, patient, doctor, department, diagnosis, procedure, and ward information. It returns 30 rows for the supplied dataset.&lt;/p&gt;

&lt;h3&gt;
  
  
  Summary of join types
&lt;/h3&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Join type&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;code&gt;INNER JOIN&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Returns matching records from both tables.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;LEFT JOIN&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Preserves all left-table records.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;RIGHT JOIN&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Preserves all right-table records.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;FULL OUTER JOIN&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Preserves records from both tables, matched where possible.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;CROSS JOIN&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Generates every possible combination.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;SELF JOIN&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Compares records within the same table.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;




&lt;h2&gt;
  
  
  2. SQL Window Functions: Analysing Data Without Losing Rows
&lt;/h2&gt;

&lt;p&gt;Window functions perform calculations across a group of related rows while keeping each individual row in the result.&lt;/p&gt;

&lt;p&gt;Unlike &lt;code&gt;GROUP BY&lt;/code&gt;, which normally combines rows into summary records, window functions allow us to display individual visits alongside calculated values.&lt;/p&gt;

&lt;h2&gt;
  
  
  Understanding OVER()
&lt;/h2&gt;

&lt;p&gt;The &lt;code&gt;OVER()&lt;/code&gt; clause defines the window of rows used by a window function.&lt;/p&gt;

&lt;h3&gt;
  
  
  Example
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt;
    &lt;span class="n"&gt;visit_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;total_bill_kes&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="k"&gt;AVG&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;total_bill_kes&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;OVER&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;overall_average_bill&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;morning&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;visit_flat&lt;/span&gt;
&lt;span class="k"&gt;ORDER&lt;/span&gt; &lt;span class="k"&gt;BY&lt;/span&gt; &lt;span class="n"&gt;visit_id&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The query calculates the average bill across all 30 visits and displays that same average beside every visit.&lt;/p&gt;

&lt;h2&gt;
  
  
  PARTITION BY
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;PARTITION BY&lt;/code&gt; divides the rows into groups for a window calculation.&lt;/p&gt;

&lt;h3&gt;
  
  
  Example
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt;
    &lt;span class="n"&gt;visit_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;doctor_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;total_bill_kes&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="k"&gt;AVG&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;total_bill_kes&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;OVER&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="k"&gt;PARTITION&lt;/span&gt; &lt;span class="k"&gt;BY&lt;/span&gt; &lt;span class="n"&gt;doctor_id&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;doctor_average_bill&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;morning&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;visit_flat&lt;/span&gt;
&lt;span class="k"&gt;ORDER&lt;/span&gt; &lt;span class="k"&gt;BY&lt;/span&gt; &lt;span class="n"&gt;doctor_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;visit_id&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This calculates a separate average bill for each doctor while retaining all individual visit records.&lt;/p&gt;

&lt;h2&gt;
  
  
  ORDER BY inside a window
&lt;/h2&gt;

&lt;p&gt;The &lt;code&gt;ORDER BY&lt;/code&gt; inside &lt;code&gt;OVER()&lt;/code&gt; defines the order used by the window calculation. It is different from the final query-level &lt;code&gt;ORDER BY&lt;/code&gt;, which controls how the results are displayed.&lt;/p&gt;

&lt;h3&gt;
  
  
  Example
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt;
    &lt;span class="n"&gt;visit_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;visit_date&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;total_bill_kes&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="k"&gt;SUM&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;total_bill_kes&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;OVER&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="k"&gt;ORDER&lt;/span&gt; &lt;span class="k"&gt;BY&lt;/span&gt; &lt;span class="n"&gt;visit_date&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;visit_id&lt;/span&gt;
        &lt;span class="k"&gt;ROWS&lt;/span&gt; &lt;span class="k"&gt;BETWEEN&lt;/span&gt; &lt;span class="n"&gt;UNBOUNDED&lt;/span&gt; &lt;span class="k"&gt;PRECEDING&lt;/span&gt;
                 &lt;span class="k"&gt;AND&lt;/span&gt; &lt;span class="k"&gt;CURRENT&lt;/span&gt; &lt;span class="k"&gt;ROW&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;running_total&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;morning&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;visit_flat&lt;/span&gt;
&lt;span class="k"&gt;ORDER&lt;/span&gt; &lt;span class="k"&gt;BY&lt;/span&gt; &lt;span class="n"&gt;visit_date&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;visit_id&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The running total increases as each visit is processed in date and visit ID order.&lt;/p&gt;

&lt;h2&gt;
  
  
  Window frames
&lt;/h2&gt;

&lt;p&gt;A window frame defines which rows are included in a calculation relative to the current row.&lt;/p&gt;

&lt;p&gt;Common frame boundaries include:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;UNBOUNDED PRECEDING&lt;/code&gt;: the beginning of the partition.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;CURRENT ROW&lt;/code&gt;: the current row.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;UNBOUNDED FOLLOWING&lt;/code&gt;: the end of the partition.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;For example, &lt;code&gt;ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW&lt;/code&gt; calculates a running total from the first row up to the current row.&lt;/p&gt;

&lt;p&gt;Explicitly specifying a frame is particularly important for functions such as &lt;code&gt;LAST_VALUE()&lt;/code&gt;, where the default frame may not extend to the final row of the entire partition.&lt;/p&gt;

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

&lt;p&gt;&lt;code&gt;ROW_NUMBER()&lt;/code&gt; assigns a unique sequential number to each row within a window.&lt;/p&gt;

&lt;h3&gt;
  
  
  Example: Number visits by bill for each doctor
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt;
    &lt;span class="n"&gt;visit_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;doctor_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;total_bill_kes&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;ROW_NUMBER&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="n"&gt;OVER&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="k"&gt;PARTITION&lt;/span&gt; &lt;span class="k"&gt;BY&lt;/span&gt; &lt;span class="n"&gt;doctor_id&lt;/span&gt;
        &lt;span class="k"&gt;ORDER&lt;/span&gt; &lt;span class="k"&gt;BY&lt;/span&gt; &lt;span class="n"&gt;total_bill_kes&lt;/span&gt; &lt;span class="k"&gt;DESC&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;visit_id&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;visit_number&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;morning&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;visit_flat&lt;/span&gt;
&lt;span class="k"&gt;ORDER&lt;/span&gt; &lt;span class="k"&gt;BY&lt;/span&gt; &lt;span class="n"&gt;doctor_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;visit_number&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Explanation
&lt;/h3&gt;

&lt;p&gt;The query partitions visits by doctor and numbers them from the highest bill to the lowest bill.&lt;/p&gt;

&lt;p&gt;For doctor &lt;code&gt;101&lt;/code&gt;, visit &lt;code&gt;1017&lt;/code&gt;, with a bill of KSh 82,000, receives row number 1. Visit &lt;code&gt;1010&lt;/code&gt;, with a bill of KSh 76,000, receives row number 2.&lt;/p&gt;

&lt;p&gt;The &lt;code&gt;visit_id&lt;/code&gt; acts as a tie-breaker when bills are equal.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;When should you use ROW_NUMBER()?&lt;/strong&gt; Use it when assigning sequential numbers, selecting the first record in a group, or identifying duplicate records.&lt;/p&gt;

&lt;h2&gt;
  
  
  RANK() and DENSE_RANK()
&lt;/h2&gt;

&lt;p&gt;Both functions rank records according to an ordering, but they handle ties differently.&lt;/p&gt;

&lt;h3&gt;
  
  
  Example
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt;
    &lt;span class="n"&gt;visit_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;total_bill_kes&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;RANK&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="n"&gt;OVER&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="k"&gt;ORDER&lt;/span&gt; &lt;span class="k"&gt;BY&lt;/span&gt; &lt;span class="n"&gt;total_bill_kes&lt;/span&gt; &lt;span class="k"&gt;DESC&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;bill_rank&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;DENSE_RANK&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="n"&gt;OVER&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="k"&gt;ORDER&lt;/span&gt; &lt;span class="k"&gt;BY&lt;/span&gt; &lt;span class="n"&gt;total_bill_kes&lt;/span&gt; &lt;span class="k"&gt;DESC&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;dense_bill_rank&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;morning&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;visit_flat&lt;/span&gt;
&lt;span class="k"&gt;ORDER&lt;/span&gt; &lt;span class="k"&gt;BY&lt;/span&gt; &lt;span class="n"&gt;bill_rank&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;visit_id&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Explanation
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;RANK()&lt;/code&gt; assigns equal ranks to tied bills and leaves gaps afterward.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;DENSE_RANK()&lt;/code&gt; assigns equal ranks to tied bills without leaving gaps.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;For example, two visits with bills of KSh 6,500 receive the same rank. With &lt;code&gt;RANK()&lt;/code&gt;, the next rank is skipped; with &lt;code&gt;DENSE_RANK()&lt;/code&gt;, the next distinct bill receives the next consecutive rank.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;When should you use them?&lt;/strong&gt; Use these functions when ranking visits, doctors, departments, or other records where tied values should share a position.&lt;/p&gt;

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

&lt;p&gt;&lt;code&gt;NTILE(n)&lt;/code&gt; divides an ordered result into approximately equal-sized groups.&lt;/p&gt;

&lt;h3&gt;
  
  
  Example: Divide visits into four billing groups
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt;
    &lt;span class="n"&gt;visit_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;total_bill_kes&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;NTILE&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;OVER&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="k"&gt;ORDER&lt;/span&gt; &lt;span class="k"&gt;BY&lt;/span&gt; &lt;span class="n"&gt;total_bill_kes&lt;/span&gt; &lt;span class="k"&gt;DESC&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;visit_id&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;bill_quartile&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;morning&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;visit_flat&lt;/span&gt;
&lt;span class="k"&gt;ORDER&lt;/span&gt; &lt;span class="k"&gt;BY&lt;/span&gt; &lt;span class="n"&gt;bill_quartile&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;total_bill_kes&lt;/span&gt; &lt;span class="k"&gt;DESC&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Explanation
&lt;/h3&gt;

&lt;p&gt;The 30 visits are divided into four groups based on their bills. Because 30 cannot be divided evenly into four whole-number groups, some groups contain seven rows, and others contain eight.&lt;/p&gt;

&lt;p&gt;Group 1 contains the highest-billed visits, while group 4 contains the lowest-billed visits.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;When should you use NTILE()?&lt;/strong&gt; Use it to divide data into quartiles or other approximately equal-sized groups for descriptive analysis. Billing quartiles are financial groupings, not measures of clinical severity.&lt;/p&gt;

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

&lt;p&gt;&lt;code&gt;LAG()&lt;/code&gt; retrieves a value from a preceding row without requiring a self join.&lt;/p&gt;

&lt;h3&gt;
  
  
  Example: Compare each patient's bill with their previous bill
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt;
    &lt;span class="n"&gt;patient_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;visit_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;visit_date&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;total_bill_kes&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;LAG&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;total_bill_kes&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;OVER&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="k"&gt;PARTITION&lt;/span&gt; &lt;span class="k"&gt;BY&lt;/span&gt; &lt;span class="n"&gt;patient_id&lt;/span&gt;
        &lt;span class="k"&gt;ORDER&lt;/span&gt; &lt;span class="k"&gt;BY&lt;/span&gt; &lt;span class="n"&gt;visit_date&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;visit_id&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;previous_bill&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;morning&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;visit_flat&lt;/span&gt;
&lt;span class="k"&gt;ORDER&lt;/span&gt; &lt;span class="k"&gt;BY&lt;/span&gt; &lt;span class="n"&gt;patient_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;visit_date&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Explanation
&lt;/h3&gt;

&lt;p&gt;The query compares each visit with the previous recorded visit for the same patient.&lt;/p&gt;

&lt;p&gt;Amina Otieno's first visit has no previous bill, so the value is &lt;code&gt;NULL&lt;/code&gt;. Her second recorded visit has a bill of KSh 4,200, and the previous bill is KSh 3,500.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;When should you use LAG()?&lt;/strong&gt; Use it to compare current and previous transactions, calculate changes over time, or examine changes between consecutive visits.&lt;/p&gt;

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

&lt;p&gt;&lt;code&gt;LEAD()&lt;/code&gt; retrieves a value from a following row.&lt;/p&gt;

&lt;h3&gt;
  
  
  Example
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt;
    &lt;span class="n"&gt;patient_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;visit_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;visit_date&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;total_bill_kes&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;LEAD&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;total_bill_kes&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;OVER&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="k"&gt;PARTITION&lt;/span&gt; &lt;span class="k"&gt;BY&lt;/span&gt; &lt;span class="n"&gt;patient_id&lt;/span&gt;
        &lt;span class="k"&gt;ORDER&lt;/span&gt; &lt;span class="k"&gt;BY&lt;/span&gt; &lt;span class="n"&gt;visit_date&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;visit_id&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;next_bill&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;morning&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;visit_flat&lt;/span&gt;
&lt;span class="k"&gt;ORDER&lt;/span&gt; &lt;span class="k"&gt;BY&lt;/span&gt; &lt;span class="n"&gt;patient_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;visit_date&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The query displays the bill from each patient's next recorded visit. If a patient has no later visit, the result is &lt;code&gt;NULL&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;For Amina Otieno, the first visit's next bill is KSh 4,200.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;When should you use LEAD()?&lt;/strong&gt; Use it to examine subsequent transactions, visits, or events in an ordered dataset.&lt;/p&gt;

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

&lt;p&gt;&lt;code&gt;FIRST_VALUE()&lt;/code&gt; returns the value from the first row of the window frame.&lt;/p&gt;

&lt;h3&gt;
  
  
  Example: Find each patient's first recorded bill
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt;
    &lt;span class="n"&gt;patient_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;visit_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;visit_date&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;total_bill_kes&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;FIRST_VALUE&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;total_bill_kes&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;OVER&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="k"&gt;PARTITION&lt;/span&gt; &lt;span class="k"&gt;BY&lt;/span&gt; &lt;span class="n"&gt;patient_id&lt;/span&gt;
        &lt;span class="k"&gt;ORDER&lt;/span&gt; &lt;span class="k"&gt;BY&lt;/span&gt; &lt;span class="n"&gt;visit_date&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;visit_id&lt;/span&gt;
        &lt;span class="k"&gt;ROWS&lt;/span&gt; &lt;span class="k"&gt;BETWEEN&lt;/span&gt; &lt;span class="n"&gt;UNBOUNDED&lt;/span&gt; &lt;span class="k"&gt;PRECEDING&lt;/span&gt;
                 &lt;span class="k"&gt;AND&lt;/span&gt; &lt;span class="n"&gt;UNBOUNDED&lt;/span&gt; &lt;span class="k"&gt;FOLLOWING&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;first_bill&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;morning&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;visit_flat&lt;/span&gt;
&lt;span class="k"&gt;ORDER&lt;/span&gt; &lt;span class="k"&gt;BY&lt;/span&gt; &lt;span class="n"&gt;patient_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;visit_date&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The query displays each patient's first recorded bill alongside every visit for that patient.&lt;/p&gt;

&lt;p&gt;For Amina Otieno, the first bill is KSh 3,500.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;When should you use FIRST_VALUE()?&lt;/strong&gt; Use it to compare each record with the first value or event in a group.&lt;/p&gt;

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

&lt;p&gt;&lt;code&gt;LAST_VALUE()&lt;/code&gt; returns the value from the last row of the window frame.&lt;/p&gt;

&lt;h3&gt;
  
  
  Example: Find each patient's last recorded bill
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt;
    &lt;span class="n"&gt;patient_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;visit_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;visit_date&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;total_bill_kes&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;LAST_VALUE&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;total_bill_kes&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;OVER&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="k"&gt;PARTITION&lt;/span&gt; &lt;span class="k"&gt;BY&lt;/span&gt; &lt;span class="n"&gt;patient_id&lt;/span&gt;
        &lt;span class="k"&gt;ORDER&lt;/span&gt; &lt;span class="k"&gt;BY&lt;/span&gt; &lt;span class="n"&gt;visit_date&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;visit_id&lt;/span&gt;
        &lt;span class="k"&gt;ROWS&lt;/span&gt; &lt;span class="k"&gt;BETWEEN&lt;/span&gt; &lt;span class="n"&gt;UNBOUNDED&lt;/span&gt; &lt;span class="k"&gt;PRECEDING&lt;/span&gt;
                 &lt;span class="k"&gt;AND&lt;/span&gt; &lt;span class="n"&gt;UNBOUNDED&lt;/span&gt; &lt;span class="k"&gt;FOLLOWING&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;last_bill&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;morning&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;visit_flat&lt;/span&gt;
&lt;span class="k"&gt;ORDER&lt;/span&gt; &lt;span class="k"&gt;BY&lt;/span&gt; &lt;span class="n"&gt;patient_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;visit_date&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The explicit window frame ensures that the calculation reaches the final row of each patient's partition.&lt;/p&gt;

&lt;p&gt;For Amina Otieno, the last recorded bill is KSh 4,200, which appears alongside both of her visits.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;When should you use LAST_VALUE()?&lt;/strong&gt; Use it to compare records with the final value in an ordered group.&lt;/p&gt;

&lt;h2&gt;
  
  
  SUM() OVER()
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;SUM()&lt;/code&gt; calculates a sum across a window without collapsing individual rows.&lt;/p&gt;

&lt;h3&gt;
  
  
  Example: Calculate cumulative hospital billing
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt;
    &lt;span class="n"&gt;visit_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;visit_date&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;total_bill_kes&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="k"&gt;SUM&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;total_bill_kes&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;OVER&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="k"&gt;ORDER&lt;/span&gt; &lt;span class="k"&gt;BY&lt;/span&gt; &lt;span class="n"&gt;visit_date&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;visit_id&lt;/span&gt;
        &lt;span class="k"&gt;ROWS&lt;/span&gt; &lt;span class="k"&gt;BETWEEN&lt;/span&gt; &lt;span class="n"&gt;UNBOUNDED&lt;/span&gt; &lt;span class="k"&gt;PRECEDING&lt;/span&gt;
                 &lt;span class="k"&gt;AND&lt;/span&gt; &lt;span class="k"&gt;CURRENT&lt;/span&gt; &lt;span class="k"&gt;ROW&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;cumulative_billing&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;morning&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;visit_flat&lt;/span&gt;
&lt;span class="k"&gt;ORDER&lt;/span&gt; &lt;span class="k"&gt;BY&lt;/span&gt; &lt;span class="n"&gt;visit_date&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;visit_id&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The cumulative total increases with each visit. The final total is &lt;strong&gt;KSh 488,000&lt;/strong&gt;, representing the sum of all 30 visit bills in the dataset.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;When should you use SUM()?&lt;/strong&gt; Use it for running totals and group-level financial calculations.&lt;/p&gt;

&lt;h2&gt;
  
  
  AVG() OVER()
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;AVG()&lt;/code&gt; calculates an average across a window.&lt;/p&gt;

&lt;h3&gt;
  
  
  Example: Compare bills with their department average
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt;
    &lt;span class="n"&gt;visit_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;department_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;total_bill_kes&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;ROUND&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="k"&gt;AVG&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;total_bill_kes&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;OVER&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="k"&gt;PARTITION&lt;/span&gt; &lt;span class="k"&gt;BY&lt;/span&gt; &lt;span class="n"&gt;department_id&lt;/span&gt;
        &lt;span class="p"&gt;),&lt;/span&gt;
        &lt;span class="mi"&gt;2&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;department_average_bill&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;morning&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;visit_flat&lt;/span&gt;
&lt;span class="k"&gt;ORDER&lt;/span&gt; &lt;span class="k"&gt;BY&lt;/span&gt; &lt;span class="n"&gt;department_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;visit_id&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Each visit remains visible alongside the average bill for its department.&lt;/p&gt;

&lt;p&gt;For department &lt;code&gt;10&lt;/code&gt;, the average bill is approximately KSh 23,344.44.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;When should you use AVG()?&lt;/strong&gt; Use it to compare individual records with their group's average without losing the original rows.&lt;/p&gt;

&lt;h2&gt;
  
  
  COUNT() OVER()
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;COUNT()&lt;/code&gt; can count rows in a window while retaining individual records.&lt;/p&gt;

&lt;h3&gt;
  
  
  Example: Count visits per patient
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt;
    &lt;span class="n"&gt;patient_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;visit_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="k"&gt;COUNT&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;OVER&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="k"&gt;PARTITION&lt;/span&gt; &lt;span class="k"&gt;BY&lt;/span&gt; &lt;span class="n"&gt;patient_id&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;patient_visit_count&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;morning&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;visit_flat&lt;/span&gt;
&lt;span class="k"&gt;ORDER&lt;/span&gt; &lt;span class="k"&gt;BY&lt;/span&gt; &lt;span class="n"&gt;patient_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;visit_id&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Patients with two recorded visits receive a count of 2 on each of their visit rows. For example, patients with IDs &lt;code&gt;1&lt;/code&gt; and &lt;code&gt;2&lt;/code&gt; each have two visits.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;When should you use COUNT()?&lt;/strong&gt; Use it to identify repeat visits and measure how frequently records appear in a group.&lt;/p&gt;

&lt;h2&gt;
  
  
  MIN() and MAX() OVER()
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;MIN()&lt;/code&gt; returns the smallest value in a window, while &lt;code&gt;MAX()&lt;/code&gt; returns the largest.&lt;/p&gt;

&lt;h3&gt;
  
  
  Example
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt;
    &lt;span class="n"&gt;visit_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;department_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;total_bill_kes&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="k"&gt;MIN&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;total_bill_kes&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;OVER&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="k"&gt;PARTITION&lt;/span&gt; &lt;span class="k"&gt;BY&lt;/span&gt; &lt;span class="n"&gt;department_id&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;minimum_department_bill&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="k"&gt;MAX&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;total_bill_kes&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;OVER&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="k"&gt;PARTITION&lt;/span&gt; &lt;span class="k"&gt;BY&lt;/span&gt; &lt;span class="n"&gt;department_id&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;maximum_department_bill&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;morning&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;visit_flat&lt;/span&gt;
&lt;span class="k"&gt;ORDER&lt;/span&gt; &lt;span class="k"&gt;BY&lt;/span&gt; &lt;span class="n"&gt;department_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;visit_id&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For department &lt;code&gt;10&lt;/code&gt;, the minimum bill is KSh 3,500, and the maximum bill is KSh 82,000.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;When should you use MIN() and MAX()?&lt;/strong&gt; Use them to identify the smallest and largest values within a group while keeping each original record visible.&lt;/p&gt;

&lt;h3&gt;
  
  
  Summary of window functions
&lt;/h3&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Function&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;code&gt;ROW_NUMBER()&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Assigns a unique sequential number to each row.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;RANK()&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Ranks rows and leaves gaps after ties.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;DENSE_RANK()&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Ranks rows without gaps after ties.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;NTILE()&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Divides ordered rows into approximately equal-sized groups.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;LAG()&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Retrieves a value from a preceding row.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;LEAD()&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Retrieves a value from a following row.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;FIRST_VALUE()&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Returns the first value in the window frame.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;LAST_VALUE()&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Returns the last value in the window frame.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;SUM()&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Calculates a sum across the window.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;AVG()&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Calculates an average across the window.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;COUNT()&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Counts rows or non-null values in the window.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;MIN()&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Returns the smallest value in the window.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;MAX()&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Returns the largest value in the window.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;




&lt;h2&gt;
  
  
  3. Common Table Expressions (CTEs): Making Queries Easier to Read
&lt;/h2&gt;

&lt;p&gt;A Common Table Expression is a named result set defined using the &lt;code&gt;WITH&lt;/code&gt; clause. It exists for the duration of one SQL statement and helps organise complex queries into logical steps.&lt;/p&gt;

&lt;h2&gt;
  
  
  Basic CTE
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Example: Find visits with bills above KSh 20,000
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;WITH&lt;/span&gt; &lt;span class="n"&gt;high_bill_visits&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="k"&gt;SELECT&lt;/span&gt;
        &lt;span class="n"&gt;visit_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;patient_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;visit_date&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;total_bill_kes&lt;/span&gt;
    &lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;morning&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;visit_flat&lt;/span&gt;
    &lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;total_bill_kes&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;20000&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;high_bill_visits&lt;/span&gt;
&lt;span class="k"&gt;ORDER&lt;/span&gt; &lt;span class="k"&gt;BY&lt;/span&gt; &lt;span class="n"&gt;total_bill_kes&lt;/span&gt; &lt;span class="k"&gt;DESC&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Explanation
&lt;/h3&gt;

&lt;p&gt;The CTE selects visits whose bills exceed KSh 20,000. The main query then retrieves the filtered records and sorts them by bill.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;When should you use a basic CTE?&lt;/strong&gt; Use it when you want to separate a filtering or calculation step from the main query to make the SQL easier to read.&lt;/p&gt;

&lt;h2&gt;
  
  
  Using multiple CTEs
&lt;/h2&gt;

&lt;p&gt;CTEs can be chained together so that later CTEs use the results of earlier ones.&lt;/p&gt;

&lt;h3&gt;
  
  
  Example: Calculate total billing by department
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;WITH&lt;/span&gt; &lt;span class="n"&gt;department_billing&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="k"&gt;SELECT&lt;/span&gt;
        &lt;span class="n"&gt;department_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="k"&gt;COUNT&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;visit_count&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="k"&gt;SUM&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;total_bill_kes&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;total_billing&lt;/span&gt;
    &lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;morning&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;visit_flat&lt;/span&gt;
    &lt;span class="k"&gt;GROUP&lt;/span&gt; &lt;span class="k"&gt;BY&lt;/span&gt; &lt;span class="n"&gt;department_id&lt;/span&gt;
&lt;span class="p"&gt;),&lt;/span&gt;
&lt;span class="n"&gt;department_report&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="k"&gt;SELECT&lt;/span&gt;
        &lt;span class="n"&gt;d&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;department_name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;db&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;visit_count&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;db&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;total_billing&lt;/span&gt;
    &lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;department_billing&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;db&lt;/span&gt;
    &lt;span class="k"&gt;INNER&lt;/span&gt; &lt;span class="k"&gt;JOIN&lt;/span&gt; &lt;span class="n"&gt;morning&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;department&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;d&lt;/span&gt;
        &lt;span class="k"&gt;ON&lt;/span&gt; &lt;span class="n"&gt;db&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;department_id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;d&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;department_id&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;department_report&lt;/span&gt;
&lt;span class="k"&gt;ORDER&lt;/span&gt; &lt;span class="k"&gt;BY&lt;/span&gt; &lt;span class="n"&gt;total_billing&lt;/span&gt; &lt;span class="k"&gt;DESC&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Explanation
&lt;/h3&gt;

&lt;p&gt;The first CTE calculates the number of visits and total billing for each department. The second joins those results to the department table to display department names.&lt;/p&gt;

&lt;h3&gt;
  
  
  Expected result
&lt;/h3&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Department&lt;/th&gt;
&lt;th&gt;Visits&lt;/th&gt;
&lt;th&gt;Total billing&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Cardiology&lt;/td&gt;
&lt;td&gt;9&lt;/td&gt;
&lt;td&gt;KSh 210,100&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Orthopedics&lt;/td&gt;
&lt;td&gt;6&lt;/td&gt;
&lt;td&gt;KSh 105,200&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;General Medicine&lt;/td&gt;
&lt;td&gt;9&lt;/td&gt;
&lt;td&gt;KSh 97,800&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Pediatrics&lt;/td&gt;
&lt;td&gt;6&lt;/td&gt;
&lt;td&gt;KSh 74,900&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;When should you use multiple CTEs?&lt;/strong&gt; Use them when a query has several logical stages, such as aggregation followed by joining and filtering.&lt;/p&gt;

&lt;h2&gt;
  
  
  Recursive CTE
&lt;/h2&gt;

&lt;p&gt;A recursive CTE references itself to generate a sequence or traverse hierarchical data. It typically contains an initial query and a recursive query connected by &lt;code&gt;UNION ALL&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;The supplied hospital database does not contain a natural parent-child hierarchy, so this example generates a calendar sequence using the hospital visit dates.&lt;/p&gt;

&lt;h3&gt;
  
  
  Example: Generate dates between the first and last visit
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;WITH&lt;/span&gt; &lt;span class="k"&gt;RECURSIVE&lt;/span&gt; &lt;span class="n"&gt;visit_calendar&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="k"&gt;MIN&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;visit_date&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;calendar_date&lt;/span&gt;
    &lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;morning&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;visit_flat&lt;/span&gt;

    &lt;span class="k"&gt;UNION&lt;/span&gt; &lt;span class="k"&gt;ALL&lt;/span&gt;

    &lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="n"&gt;calendar_date&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;
    &lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;visit_calendar&lt;/span&gt;
    &lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;calendar_date&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="k"&gt;MAX&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;visit_date&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;morning&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;visit_flat&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="n"&gt;calendar_date&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;visit_calendar&lt;/span&gt;
&lt;span class="k"&gt;ORDER&lt;/span&gt; &lt;span class="k"&gt;BY&lt;/span&gt; &lt;span class="n"&gt;calendar_date&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Explanation
&lt;/h3&gt;

&lt;p&gt;The initial query selects the earliest visit date. The recursive query adds one day to the previous date until it reaches the latest visit date.&lt;/p&gt;

&lt;p&gt;The result contains 15 dates, from &lt;code&gt;2026-06-01&lt;/code&gt; to &lt;code&gt;2026-06-15&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;When should you use a recursive CTE?&lt;/strong&gt; Use it for generating sequences, traversing hierarchies, or processing data that requires repeated steps.&lt;/p&gt;

&lt;h2&gt;
  
  
  CTEs with joins and aggregations
&lt;/h2&gt;

&lt;p&gt;CTEs can combine aggregation and joins to produce readable summary reports.&lt;/p&gt;

&lt;h3&gt;
  
  
  Example: Department billing and average satisfaction
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;WITH&lt;/span&gt; &lt;span class="n"&gt;department_metrics&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="k"&gt;SELECT&lt;/span&gt;
        &lt;span class="n"&gt;department_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="k"&gt;COUNT&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;visit_count&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="k"&gt;SUM&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;total_bill_kes&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;total_billing&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;ROUND&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;AVG&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;patient_satisfaction_score&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;average_satisfaction&lt;/span&gt;
    &lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;morning&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;visit_flat&lt;/span&gt;
    &lt;span class="k"&gt;GROUP&lt;/span&gt; &lt;span class="k"&gt;BY&lt;/span&gt; &lt;span class="n"&gt;department_id&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;SELECT&lt;/span&gt;
    &lt;span class="n"&gt;d&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;department_name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;dm&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;visit_count&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;dm&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;total_billing&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;dm&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;average_satisfaction&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;department_metrics&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;dm&lt;/span&gt;
&lt;span class="k"&gt;INNER&lt;/span&gt; &lt;span class="k"&gt;JOIN&lt;/span&gt; &lt;span class="n"&gt;morningdepartment&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;d&lt;/span&gt;
    &lt;span class="k"&gt;ON&lt;/span&gt; &lt;span class="n"&gt;dm&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;department_id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;d&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;department_id&lt;/span&gt;
&lt;span class="k"&gt;ORDER&lt;/span&gt; &lt;span class="k"&gt;BY&lt;/span&gt; &lt;span class="n"&gt;dm&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;total_billing&lt;/span&gt; &lt;span class="k"&gt;DESC&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Explanation
&lt;/h3&gt;

&lt;p&gt;The CTE calculates visit counts, total billing, and average satisfaction scores. The main query adds department names by joining the aggregated result to &lt;code&gt;department&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;This produces a report that combines operational and financial metrics.&lt;/p&gt;

&lt;h2&gt;
  
  
  CTEs versus subqueries
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Feature&lt;/th&gt;
&lt;th&gt;CTE&lt;/th&gt;
&lt;th&gt;Subquery&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Definition&lt;/td&gt;
&lt;td&gt;Uses &lt;code&gt;WITH&lt;/code&gt;.&lt;/td&gt;
&lt;td&gt;Nested inside another query.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Readability&lt;/td&gt;
&lt;td&gt;Often clearer for multi-step logic.&lt;/td&gt;
&lt;td&gt;Often concise for simple calculations.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Reuse&lt;/td&gt;
&lt;td&gt;Can be referenced multiple times in one statement.&lt;/td&gt;
&lt;td&gt;May need to be repeated if the same logic is reused.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Recursive processing&lt;/td&gt;
&lt;td&gt;Supports recursive queries using &lt;code&gt;WITH RECURSIVE&lt;/code&gt;.&lt;/td&gt;
&lt;td&gt;Not the usual approach for recursive processing.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Performance&lt;/td&gt;
&lt;td&gt;Depends on the query and execution plan.&lt;/td&gt;
&lt;td&gt;Also depends on the query and execution plan.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Neither approach is automatically faster. Choose based on readability, maintainability, and the execution plan.&lt;/p&gt;




&lt;h2&gt;
  
  
  4. Subqueries: Queries Inside Other Queries
&lt;/h2&gt;

&lt;p&gt;A subquery is a query nested inside another SQL statement. It can return a single value, multiple rows, or a table-like result.&lt;/p&gt;

&lt;p&gt;Subqueries can appear in &lt;code&gt;SELECT&lt;/code&gt;, &lt;code&gt;FROM&lt;/code&gt;, and &lt;code&gt;WHERE&lt;/code&gt; clauses.&lt;/p&gt;

&lt;h2&gt;
  
  
  Scalar subquery
&lt;/h2&gt;

&lt;p&gt;A scalar subquery returns a single value: one row and one column.&lt;/p&gt;

&lt;h3&gt;
  
  
  Example: Display the average bill alongside each visit
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt;
    &lt;span class="n"&gt;visit_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;total_bill_kes&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="k"&gt;AVG&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;total_bill_kes&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;morning&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;visit_flat&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;overall_average_bill&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;morning&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;visit_flat&lt;/span&gt;
&lt;span class="k"&gt;ORDER&lt;/span&gt; &lt;span class="k"&gt;BY&lt;/span&gt; &lt;span class="n"&gt;visit_id&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Explanation
&lt;/h3&gt;

&lt;p&gt;The inner query calculates the average bill across all visits. The outer query displays that value alongside every visit.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;When should you use a scalar subquery? **&lt;/strong&gt; Use it when a single calculated value is needed within a larger query.&lt;/p&gt;

&lt;h2&gt;
  
  
  Non-correlated subquery
&lt;/h2&gt;

&lt;p&gt;A non-correlated subquery does not depend on values from the outer query.&lt;/p&gt;

&lt;h3&gt;
  
  
  Example: Find visits with bills above the overall average
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt;
    &lt;span class="n"&gt;visit_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;patient_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;total_bill_kes&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;morning&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;visit_flat&lt;/span&gt;
&lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;total_bill_kes&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="k"&gt;AVG&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;total_bill_kes&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;morning&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;visit_flat&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;ORDER&lt;/span&gt; &lt;span class="k"&gt;BY&lt;/span&gt; &lt;span class="n"&gt;total_bill_kes&lt;/span&gt; &lt;span class="k"&gt;DESC&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Explanation
&lt;/h3&gt;

&lt;p&gt;The inner query calculates the overall average bill. The outer query returns visits whose bills exceed that value.&lt;/p&gt;

&lt;p&gt;The subquery does not reference the outer query, so it is non-correlated.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;When should you use it? **&lt;/strong&gt; Use it when comparing records against a general benchmark, such as an overall average.&lt;/p&gt;

&lt;h2&gt;
  
  
  Correlated subquery
&lt;/h2&gt;

&lt;p&gt;A correlated subquery refers to a value from the outer query. Its result depends on the current outer row.&lt;/p&gt;

&lt;h3&gt;
  
  
  Example: Find visits above the average bill for that patient
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt;
    &lt;span class="n"&gt;v&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;visit_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;v&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;patient_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;v&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;total_bill_kes&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;morning&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;visit_flat&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;v&lt;/span&gt;
&lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;v&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;total_bill_kes&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="k"&gt;AVG&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;v2&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;total_bill_kes&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;morning&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;visit_flat&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;v2&lt;/span&gt;
    &lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;v2&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;patient_id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;v&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;patient_id&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;ORDER&lt;/span&gt; &lt;span class="k"&gt;BY&lt;/span&gt; &lt;span class="n"&gt;v&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;patient_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;v&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;visit_id&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Explanation
&lt;/h3&gt;

&lt;p&gt;For each visit, the inner query calculates the average bill for the same patient. The outer query returns visits whose bills exceed that patient's average.&lt;/p&gt;

&lt;p&gt;Patients with only one visit will not appear because their bill equals their own average.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;When should you use it?&lt;/strong&gt; Use it for comparisons within a group, such as comparing each visit with its patient's average.&lt;/p&gt;

&lt;h2&gt;
  
  
  Nested subquery
&lt;/h2&gt;

&lt;p&gt;A nested subquery contains another subquery inside it.&lt;/p&gt;

&lt;h3&gt;
  
  
  Example: Find visits above the average bill for chronic-disease diagnoses
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt;
    &lt;span class="n"&gt;visit_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;diagnosis_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;total_bill_kes&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;morning&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;visit_flat&lt;/span&gt;
&lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;total_bill_kes&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="k"&gt;AVG&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;total_bill_kes&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;morning&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;visit_flat&lt;/span&gt;
    &lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;diagnosis_id&lt;/span&gt; &lt;span class="k"&gt;IN&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="n"&gt;diagnosis_id&lt;/span&gt;
        &lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;morning&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;diagnosis&lt;/span&gt;
        &lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;diagnosis_category&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'Chronic Disease'&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;ORDER&lt;/span&gt; &lt;span class="k"&gt;BY&lt;/span&gt; &lt;span class="n"&gt;total_bill_kes&lt;/span&gt; &lt;span class="k"&gt;DESC&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Explanation
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;The innermost query identifies diagnoses in the chronic-disease category.&lt;/li&gt;
&lt;li&gt;The middle query calculates the average bill for visits with those diagnoses.&lt;/li&gt;
&lt;li&gt;The outer query returns visits whose bills exceed that average.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The outer query is not restricted to chronic-disease visits; it compares all visits with the calculated benchmark.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;When should you use it?&lt;/strong&gt; Use nested subqueries when one query must identify a set of records before another query can calculate or filter results.&lt;/p&gt;

&lt;h2&gt;
  
  
  Subqueries using IN
&lt;/h2&gt;

&lt;p&gt;The &lt;code&gt;IN&lt;/code&gt; operator checks whether a value matches any value returned by a list or subquery.&lt;/p&gt;

&lt;h3&gt;
  
  
  Example: Find visits associated with chronic diseases
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt;
    &lt;span class="n"&gt;visit_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;diagnosis_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;total_bill_kes&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;morning&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;visit_flat&lt;/span&gt;
&lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;diagnosis_id&lt;/span&gt; &lt;span class="k"&gt;IN&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="n"&gt;diagnosis_id&lt;/span&gt;
    &lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;morning&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;diagnosis&lt;/span&gt;
    &lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;diagnosis_category&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'Chronic Disease'&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;ORDER&lt;/span&gt; &lt;span class="k"&gt;BY&lt;/span&gt; &lt;span class="n"&gt;visit_id&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The inner query returns diagnosis IDs in the chronic-disease category. The outer query returns visits associated with those diagnoses.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;When should you use IN?&lt;/strong&gt; Use it when filtering records based on membership in a set of values.&lt;/p&gt;

&lt;h2&gt;
  
  
  Subqueries using EXISTS
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;EXISTS&lt;/code&gt; checks whether a subquery returns at least one row.&lt;/p&gt;

&lt;h3&gt;
  
  
  Example: Find patients with multiple visits
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt;
    &lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;patient_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;patient_name&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;morning&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;patients&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;p&lt;/span&gt;
&lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="k"&gt;EXISTS&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;
    &lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;morning&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;visit_flat&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;v&lt;/span&gt;
    &lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;v&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;patient_id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;patient_id&lt;/span&gt;
    &lt;span class="k"&gt;GROUP&lt;/span&gt; &lt;span class="k"&gt;BY&lt;/span&gt; &lt;span class="n"&gt;v&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;patient_id&lt;/span&gt;
    &lt;span class="k"&gt;HAVING&lt;/span&gt; &lt;span class="k"&gt;COUNT&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;ORDER&lt;/span&gt; &lt;span class="k"&gt;BY&lt;/span&gt; &lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;patient_id&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Explanation
&lt;/h3&gt;

&lt;p&gt;For each patient, the subquery checks whether that patient has more than one visit. &lt;code&gt;EXISTS&lt;/code&gt; returns true when the subquery finds a matching group.&lt;/p&gt;

&lt;p&gt;Patients with multiple visits include Amina Otieno, John Kariuki, Esther Achieng, Lucy Wambui, and Peter Ouma.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;When should you use EXISTS?&lt;/strong&gt; Use it when the goal is to check whether related records exist rather than retrieve those records.&lt;/p&gt;

&lt;h2&gt;
  
  
  Subqueries using ANY
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;ANY&lt;/code&gt; compares a value with the results of a subquery. The condition is true if it is satisfied for at least one returned value.&lt;/p&gt;

&lt;h3&gt;
  
  
  Example: Find visits with bills greater than at least one inpatient bill
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt;
    &lt;span class="n"&gt;visit_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;admission_type&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;total_bill_kes&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;morning&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;visit_flat&lt;/span&gt;
&lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;total_bill_kes&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="k"&gt;ANY&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="n"&gt;total_bill_kes&lt;/span&gt;
    &lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;morning&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;visit_flat&lt;/span&gt;
    &lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;admission_type&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'Inpatient'&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;ORDER&lt;/span&gt; &lt;span class="k"&gt;BY&lt;/span&gt; &lt;span class="n"&gt;total_bill_kes&lt;/span&gt; &lt;span class="k"&gt;DESC&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Explanation
&lt;/h3&gt;

&lt;p&gt;The subquery returns the bills of inpatient visits. A visit satisfies the condition if its bill is greater than at least one of those bills.&lt;/p&gt;

&lt;p&gt;For a non-empty set of values, &lt;code&gt;&amp;gt; ANY&lt;/code&gt; is equivalent to being greater than the smallest value in that set.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;When should you use ANY?&lt;/strong&gt; Use it when a comparison needs to be satisfied by at least one value in a result set.&lt;/p&gt;

&lt;h2&gt;
  
  
  Subqueries using ALL
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;ALL&lt;/code&gt; compares a value with every value returned by a subquery. The condition must be true for all returned values.&lt;/p&gt;

&lt;h3&gt;
  
  
  Example: Find visits whose bills are at least as high as every inpatient bill
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt;
    &lt;span class="n"&gt;visit_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;admission_type&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;total_bill_kes&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;morning&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;visit_flat&lt;/span&gt;
&lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;total_bill_kes&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;=&lt;/span&gt; &lt;span class="k"&gt;ALL&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="n"&gt;total_bill_kes&lt;/span&gt;
    &lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;morning&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;visit_flat&lt;/span&gt;
    &lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;admission_type&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'Inpatient'&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;ORDER&lt;/span&gt; &lt;span class="k"&gt;BY&lt;/span&gt; &lt;span class="n"&gt;total_bill_kes&lt;/span&gt; &lt;span class="k"&gt;DESC&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Explanation
&lt;/h3&gt;

&lt;p&gt;The condition requires a visit's bill to be greater than or equal to every inpatient bill.&lt;/p&gt;

&lt;p&gt;The highest inpatient bill in the dataset is KSh 82,000, recorded for visit &lt;code&gt;1017&lt;/code&gt;. Therefore, that visit satisfies the condition.&lt;/p&gt;

&lt;p&gt;If we used &lt;code&gt;&amp;gt; ALL&lt;/code&gt; instead, no rows would be returned because no bill exceeds KSh 82,000.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;When should you use ALL?&lt;/strong&gt; Use it when a value must satisfy a comparison against every value in a set.&lt;/p&gt;

&lt;h2&gt;
  
  
  Subqueries in the SELECT clause
&lt;/h2&gt;

&lt;p&gt;A subquery in the &lt;code&gt;SELECT&lt;/code&gt; clause can add a calculated value to each output row.&lt;/p&gt;

&lt;h3&gt;
  
  
  Example: Display the highest bill alongside each visit
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt;
    &lt;span class="n"&gt;visit_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;total_bill_kes&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="k"&gt;MAX&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;total_bill_kes&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;morning&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;visit_flat&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;highest_bill_in_dataset&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;morning&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;visit_flat&lt;/span&gt;
&lt;span class="k"&gt;ORDER&lt;/span&gt; &lt;span class="k"&gt;BY&lt;/span&gt; &lt;span class="n"&gt;visit_id&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;highest_bill_in_dataset&lt;/code&gt; column displays KSh 82,000 on every row, while the individual visit bills vary.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;When should you use it? **&lt;/strong&gt; Use a scalar subquery in &lt;code&gt;SELECT&lt;/code&gt; when each row needs a shared benchmark or summary value.&lt;/p&gt;

&lt;h2&gt;
  
  
  Subqueries in the FROM clause
&lt;/h2&gt;

&lt;p&gt;A subquery in the &lt;code&gt;FROM&lt;/code&gt; clause acts as a derived table. In PostgreSQL, it must have an alias.&lt;/p&gt;

&lt;h3&gt;
  
  
  Example: Count visits for each patient
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt;
    &lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;patient_name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;patient_summary&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;visit_count&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="k"&gt;SELECT&lt;/span&gt;
        &lt;span class="n"&gt;patient_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="k"&gt;COUNT&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;visit_count&lt;/span&gt;
    &lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;morning&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;visit_flat&lt;/span&gt;
    &lt;span class="k"&gt;GROUP&lt;/span&gt; &lt;span class="k"&gt;BY&lt;/span&gt; &lt;span class="n"&gt;patient_id&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;patient_summary&lt;/span&gt;
&lt;span class="k"&gt;INNER&lt;/span&gt; &lt;span class="k"&gt;JOIN&lt;/span&gt; &lt;span class="n"&gt;morning&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;patients&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;p&lt;/span&gt;
    &lt;span class="k"&gt;ON&lt;/span&gt; &lt;span class="n"&gt;patient_summary&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;patient_id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;patient_id&lt;/span&gt;
&lt;span class="k"&gt;ORDER&lt;/span&gt; &lt;span class="k"&gt;BY&lt;/span&gt; &lt;span class="n"&gt;patient_summaryvisit_count&lt;/span&gt; &lt;span class="k"&gt;DESC&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;patient_name&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Explanation
&lt;/h3&gt;

&lt;p&gt;The derived table calculates the number of visits per patient. The outer query joins those counts to &lt;code&gt;patients&lt;/code&gt; to display patient names.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;When should you use it? **&lt;/strong&gt; Use a derived table when an aggregated or transformed result needs to be used as input for a larger query.&lt;/p&gt;

&lt;h3&gt;
  
  
  Summary of subquery types
&lt;/h3&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Type&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;Scalar&lt;/td&gt;
&lt;td&gt;Returns one value.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Non-correlated&lt;/td&gt;
&lt;td&gt;Does not depend on the outer query.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Correlated&lt;/td&gt;
&lt;td&gt;References values from the outer query.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Nested&lt;/td&gt;
&lt;td&gt;Contains another subquery.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;IN&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Checks membership in a set of values.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;EXISTS&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Checks whether a matching row exists.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;ANY&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Requires a comparison to be true for at least one returned value.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;ALL&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Requires a comparison to be true for every returned value.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;In &lt;code&gt;SELECT&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;Adds a calculated value to the output.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;In &lt;code&gt;FROM&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;Creates a derived table.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;In &lt;code&gt;WHERE&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;Filters records using another query's result.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;




&lt;h2&gt;
  
  
  5. CTEs vs Subqueries
&lt;/h2&gt;

&lt;p&gt;Both CTEs and subqueries help structure query logic, but they differ in how the logic is expressed.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Feature&lt;/th&gt;
&lt;th&gt;CTE&lt;/th&gt;
&lt;th&gt;Subquery&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Definition&lt;/td&gt;
&lt;td&gt;Uses &lt;code&gt;WITH&lt;/code&gt;.&lt;/td&gt;
&lt;td&gt;Nested inside another query.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Readability&lt;/td&gt;
&lt;td&gt;Often clearer for multi-step logic.&lt;/td&gt;
&lt;td&gt;Often concise for simple logic.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Reuse&lt;/td&gt;
&lt;td&gt;Can be referenced multiple times within one statement.&lt;/td&gt;
&lt;td&gt;May need to be repeated if the same logic is reused.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Recursive queries&lt;/td&gt;
&lt;td&gt;Supported using &lt;code&gt;WITH RECURSIVE&lt;/code&gt;.&lt;/td&gt;
&lt;td&gt;Not the usual approach for recursive processing.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Performance&lt;/td&gt;
&lt;td&gt;Depends on the query plan.&lt;/td&gt;
&lt;td&gt;Also depends on the query plan.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Neither approach is universally faster. The choice should depend on clarity, maintainability, and the execution plan produced by PostgreSQL.&lt;/p&gt;

&lt;h2&gt;
  
  
  6. Combining These Concepts
&lt;/h2&gt;

&lt;p&gt;The real value of SQL comes from combining these techniques to answer practical questions.&lt;/p&gt;

&lt;p&gt;For example, we can rank visits by bill within each department, then join the result to the department table to display department names.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;WITH&lt;/span&gt; &lt;span class="n"&gt;RankedVisits&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="k"&gt;SELECT&lt;/span&gt;
        &lt;span class="n"&gt;department_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;visit_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;patient_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;total_bill_kes&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;ROW_NUMBER&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="n"&gt;OVER&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="k"&gt;PARTITION&lt;/span&gt; &lt;span class="k"&gt;BY&lt;/span&gt; &lt;span class="n"&gt;department_id&lt;/span&gt;
            &lt;span class="k"&gt;ORDER&lt;/span&gt; &lt;span class="k"&gt;BY&lt;/span&gt; &lt;span class="n"&gt;total_bill_kes&lt;/span&gt; &lt;span class="k"&gt;DESC&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;visit_id&lt;/span&gt;
        &lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;rn&lt;/span&gt;
    &lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;morning&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;visit_flat&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;SELECT&lt;/span&gt;
    &lt;span class="n"&gt;dep&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;department_name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;visit_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;patient_name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;total_bill_kes&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;RankedVisits&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;r&lt;/span&gt;
&lt;span class="k"&gt;INNER&lt;/span&gt; &lt;span class="k"&gt;JOIN&lt;/span&gt; &lt;span class="n"&gt;morning&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;department&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;dep&lt;/span&gt;
    &lt;span class="k"&gt;ON&lt;/span&gt; &lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;department_id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;dep&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;department_id&lt;/span&gt;
&lt;span class="k"&gt;INNER&lt;/span&gt; &lt;span class="k"&gt;JOIN&lt;/span&gt; &lt;span class="n"&gt;morning&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;patients&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;p&lt;/span&gt;
    &lt;span class="k"&gt;ON&lt;/span&gt; &lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;patient_id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;patient_id&lt;/span&gt;
&lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;rn&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;
&lt;span class="k"&gt;ORDER&lt;/span&gt; &lt;span class="k"&gt;BY&lt;/span&gt; &lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;total_bill_kes&lt;/span&gt; &lt;span class="k"&gt;DESC&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This query combines three concepts:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;CTE:&lt;/strong&gt; Organises the visit-ranking logic.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Window function:&lt;/strong&gt; Ranks visits within each department.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Joins:&lt;/strong&gt; Retrieves department names and patient names.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The result contains one highest-billed visit for each department. The &lt;code&gt;visit_id&lt;/code&gt; tie-breaker makes the ranking consistent when bills are equal.&lt;/p&gt;

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

&lt;p&gt;Joins, window functions, CTEs, and subqueries are essential tools for writing effective SQL queries.&lt;/p&gt;

&lt;p&gt;Joins combine related data from multiple tables. Window functions analyse and compare rows without losing individual records. CTEs organise complex queries into readable steps, while recursive CTEs support sequences and hierarchical processing. Subqueries allow one query to use the results of another for filtering, calculations, and comparisons.&lt;/p&gt;

&lt;p&gt;Working with the hospital dataset demonstrates how these concepts can answer practical questions about patient visits, doctor activity, diagnoses, department billing, and repeat visits.&lt;/p&gt;

&lt;p&gt;Understanding the problem before choosing a SQL technique is essential. With practice, these concepts can help you write queries that are clearer and more maintainable.&lt;/p&gt;

</description>
      <category>sql</category>
      <category>postgressql</category>
      <category>database</category>
      <category>datascience</category>
    </item>
    <item>
      <title>From Raw Data to Business Insights: Building a JCars Logistics Power BI Dashboard</title>
      <dc:creator>Victoria Ndei</dc:creator>
      <pubDate>Wed, 30 Sep 2026 12:40:21 +0000</pubDate>
      <link>https://dev.to/victoria_ndei_07224d0b8db/from-raw-data-to-business-insights-building-a-jcars-logistics-power-bi-dashboard-3ilf</link>
      <guid>https://dev.to/victoria_ndei_07224d0b8db/from-raw-data-to-business-insights-building-a-jcars-logistics-power-bi-dashboard-3ilf</guid>
      <description>&lt;h2&gt;
  
  
  Introduction
&lt;/h2&gt;

&lt;p&gt;Business Intelligence is not simply about creating attractive dashboards. A reliable dashboard starts with understanding the data, identifying quality problems, applying appropriate transformations, building a suitable analytical model, and developing calculations that answer meaningful business questions.&lt;/p&gt;

&lt;p&gt;For this project, I worked with the JCars Logistics vehicle sales and operations dataset to develop an interactive Power BI solution for analysing sales performance, profitability, customers, vehicles, branches, logistics operations, and unusual business patterns.&lt;/p&gt;

&lt;p&gt;The project followed a complete BI workflow:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Raw Data → Data Investigation → Cleaning &amp;amp; Transformation → Data Modelling → DAX → Dashboard Development → Analysis → Insights → Recommendations&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The goal was to transform a raw and inconsistent dataset into a structured analytical solution that could support management-level decision-making.&lt;/p&gt;

&lt;p&gt;In this article, I will walk through the problems I encountered in the raw data, how I cleaned and prepared it, how I designed the data model, how I developed the DAX calculations, and how the final report was designed to turn the data into useful business information.&lt;/p&gt;

&lt;h2&gt;
  
  
  Receiving and Understanding the Raw Dataset
&lt;/h2&gt;

&lt;p&gt;The project started with a raw CSV dataset containing vehicle sales, customer, financial, operational, and location-related information.&lt;/p&gt;

&lt;p&gt;At first, the dataset looked like a straightforward table that could be imported directly into Power BI. However, before building any visuals, I needed to determine whether the data was actually reliable enough for analysis.&lt;/p&gt;

&lt;p&gt;This was an important step because errors in the source data could eventually appear as incorrect KPIs, misleading comparisons, or inaccurate business conclusions.&lt;/p&gt;

&lt;h3&gt;
  
  
  What I Investigated
&lt;/h3&gt;

&lt;p&gt;I performed a data-quality investigation covering several areas:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Missing and blank values&lt;/li&gt;
&lt;li&gt;Null values&lt;/li&gt;
&lt;li&gt;Duplicate or repeated records&lt;/li&gt;
&lt;li&gt;Invalid numerical values&lt;/li&gt;
&lt;li&gt;Negative values&lt;/li&gt;
&lt;li&gt;Text appearing in numerical fields&lt;/li&gt;
&lt;li&gt;Inconsistent currency symbols and labels&lt;/li&gt;
&lt;li&gt;Values containing &lt;code&gt;K&lt;/code&gt; and &lt;code&gt;M&lt;/code&gt; suffixes&lt;/li&gt;
&lt;li&gt;Invalid and inconsistent dates&lt;/li&gt;
&lt;li&gt;Placeholder values such as &lt;code&gt;-&lt;/code&gt; and &lt;code&gt;TBD.&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Ratings outside the expected range&lt;/li&gt;
&lt;li&gt;Inconsistent discount formats&lt;/li&gt;
&lt;li&gt;Inconsistent categorical values&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Rather than assuming that every unusual value was an error, I considered the meaning of each field and determined an appropriate treatment.&lt;/p&gt;

&lt;p&gt;For example, a negative value could indicate a genuine business adjustment or could be a data-entry or scraping issue. Automatically deleting such records could therefore remove potentially useful information.&lt;/p&gt;

&lt;h3&gt;
  
  
  Why the Investigation Mattered
&lt;/h3&gt;

&lt;p&gt;The investigation established that data preparation needed to happen before the analytical model was built.&lt;/p&gt;

&lt;p&gt;This changed the workflow from simply:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Import → Visualize&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;to:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Investigate → Clean → Validate → Model → Analyze&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;That distinction became one of the most important lessons from the project: a dashboard is only as reliable as the data and business rules behind it.&lt;/p&gt;

&lt;h2&gt;
  
  
  Data Cleaning and Transformation with Power Query
&lt;/h2&gt;

&lt;p&gt;After identifying the data-quality issues, I moved into the cleaning and transformation stage using &lt;strong&gt;Power Query in Power BI&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;The objective was not simply to make the dataset look cleaner. Each transformation needed to make the data more consistent and suitable for analysis.&lt;/p&gt;

&lt;h3&gt;
  
  
  Main Cleaning Steps
&lt;/h3&gt;

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

&lt;ol&gt;
&lt;li&gt;Promoting the correct row to column headers.&lt;/li&gt;
&lt;li&gt;Assigning appropriate data types to the columns.&lt;/li&gt;
&lt;li&gt;Cleaning text fields and removing unnecessary spaces.&lt;/li&gt;
&lt;li&gt;Handling blank, null, and placeholder values.&lt;/li&gt;
&lt;li&gt;Cleaning numerical fields that contained text or symbols.&lt;/li&gt;
&lt;li&gt;Standardizing monetary fields.&lt;/li&gt;
&lt;li&gt;Cleaning and standardizing discounts.&lt;/li&gt;
&lt;li&gt;Standardizing customer ratings.&lt;/li&gt;
&lt;li&gt;Cleaning and standardizing dates.&lt;/li&gt;
&lt;li&gt;Handling negative values according to the documented business rules.&lt;/li&gt;
&lt;li&gt;Creating the keys required for the analytical model.&lt;/li&gt;
&lt;li&gt;Preparing the fact and dimension tables.&lt;/li&gt;
&lt;li&gt;Validating the cleaned data before continuing to modelling.&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  Handling Missing Values
&lt;/h3&gt;

&lt;p&gt;Some fields contained missing information. Instead of leaving these values untreated, I applied documented business rules where a reasonable default was appropriate.&lt;/p&gt;

&lt;p&gt;Examples included:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Missing discount → &lt;code&gt;0&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Missing units sold → &lt;code&gt;1&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Missing review count → &lt;code&gt;0&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Missing delivery fee → &lt;code&gt;0&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Missing logistics cost → &lt;code&gt;0&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Missing customer rating → &lt;code&gt;3&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Missing customer age → &lt;code&gt;35&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Missing vehicle year → &lt;code&gt;2022&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Missing order ID → &lt;code&gt;UNKNOWN&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;These assumptions were documented so that the analytical results could be interpreted in the context of how incomplete records had been treated.&lt;/p&gt;

&lt;h3&gt;
  
  
  Cleaning Negative Values
&lt;/h3&gt;

&lt;p&gt;Negative values required additional consideration because their meaning depends on the business field.&lt;/p&gt;

&lt;p&gt;Where a negative value represented a value that should logically be non-negative, the data was cleaned according to the defined business rule. I avoided making arbitrary changes where the original business meaning could not be established.&lt;/p&gt;

&lt;p&gt;This helped maintain a balance between data correction and preservation of potentially meaningful records.&lt;/p&gt;

&lt;h3&gt;
  
  
  Validation
&lt;/h3&gt;

&lt;p&gt;After the transformation steps, I checked the cleaned data to confirm that:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Numerical columns contained appropriate numerical values.&lt;/li&gt;
&lt;li&gt;Dates were consistently formatted.&lt;/li&gt;
&lt;li&gt;Categories were standardized.&lt;/li&gt;
&lt;li&gt;Missing values had been handled according to the documented rules.&lt;/li&gt;
&lt;li&gt;The resulting data could be loaded into the analytical model.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The validation stage was important because cleaning data without checking the results can introduce new errors while attempting to fix old ones.&lt;/p&gt;

&lt;h2&gt;
  
  
  Currency Handling and Business Assumptions
&lt;/h2&gt;

&lt;p&gt;One of the more challenging parts of preparing the dataset was handling monetary values.&lt;/p&gt;

&lt;p&gt;The raw data contained different currency symbols, currency labels, and numerical representations. Some values also used suffixes such as &lt;code&gt;K&lt;/code&gt; and &lt;code&gt;M&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Because financial calculations depend heavily on consistent units, currency handling had to be addressed before building the final measures.&lt;/p&gt;

&lt;h3&gt;
  
  
  Currency Standardisation
&lt;/h3&gt;

&lt;p&gt;The final reporting currency for the project is &lt;strong&gt;Kenyan Shillings (KES)&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;During Power Query transformation, currency symbols and textual indicators were cleaned so that monetary fields could be used consistently in calculations.&lt;/p&gt;

&lt;p&gt;Values using &lt;code&gt;K&lt;/code&gt; and &lt;code&gt;M&lt;/code&gt; suffixes were interpreted according to their numerical meaning.&lt;/p&gt;

&lt;p&gt;Where foreign-currency labels appeared, but there was not enough reliable information to establish and apply a defensible exchange rate, I did not introduce an unsupported conversion.&lt;/p&gt;

&lt;p&gt;This was an important modelling decision because applying an assumed exchange rate without sufficient evidence could create a different type of data-quality problem.&lt;/p&gt;

&lt;h3&gt;
  
  
  Revenue Calculation
&lt;/h3&gt;

&lt;p&gt;The project used the following business rule for revenue:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Revenue = Units Sold × Unit Selling Price × (1 − Discount) + Delivery Fee&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;This calculation was implemented in the analytical layer so that revenue could be consistently analysed across the report.&lt;/p&gt;

&lt;h3&gt;
  
  
  Other Assumptions
&lt;/h3&gt;

&lt;p&gt;Several additional assumptions were required when dealing with incomplete records.&lt;/p&gt;

&lt;p&gt;For example, missing discounts were treated as zero, while missing delivery and logistics fees were treated as zero. Missing ratings were assigned a neutral value of 3.&lt;/p&gt;

&lt;p&gt;These decisions were documented rather than hidden inside the transformation process.&lt;/p&gt;

&lt;h3&gt;
  
  
  Why Documentation Matters
&lt;/h3&gt;

&lt;p&gt;An important lesson from this stage was that data cleaning is not purely technical.&lt;/p&gt;

&lt;p&gt;When the source data does not provide complete information, the analyst has to make decisions about how the data should be interpreted. Those decisions become part of the analytical methodology and therefore need to be transparent.&lt;/p&gt;

&lt;p&gt;The assumptions used in this project are documented in the accompanying project documentation in the GitHub repository.&lt;/p&gt;

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

&lt;p&gt;Once the data had been cleaned and validated, I moved from data preparation into data modelling.&lt;/p&gt;

&lt;p&gt;The raw dataset was originally structured as a large flat table. Although a flat table can be useful for initial investigation, it is not the most suitable structure for a scalable analytical report.&lt;/p&gt;

&lt;p&gt;I therefore created a &lt;strong&gt;star-schema data model&lt;/strong&gt; consisting of a central fact table surrounded by descriptive dimension 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%2Fq78di6mwf5voyykjfmx4.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%2Fq78di6mwf5voyykjfmx4.png" alt="jcars" width="800" height="450"&gt;&lt;/a&gt;&lt;br&gt;
_Figure 1 illustrates how the dataset looked after cleaning. _&lt;/p&gt;

&lt;h3&gt;
  
  
  Fact Table
&lt;/h3&gt;

&lt;p&gt;The central table is &lt;strong&gt;FactSales&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%2Fliai1etovbnosmp7ybcv.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%2Fliai1etovbnosmp7ybcv.png" alt="factsales" width="800" height="450"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;Figure 2.1 illustrates the FactSales Table&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;It contains the transaction-level information required for quantitative analysis, including measures related to sales, revenue, costs, discounts, delivery, logistics, ratings, and other transaction indicators.&lt;/p&gt;

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

&lt;p&gt;I created the following dimensions:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Dim Vehicle&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%2Fsnael9x29sust3cfwcv0.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%2Fsnael9x29sust3cfwcv0.png" alt="dim" width="800" height="450"&gt;&lt;/a&gt;&lt;br&gt;
Contains descriptive vehicle attributes such as:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Vehicle Key&lt;/li&gt;
&lt;li&gt;Car Make&lt;/li&gt;
&lt;li&gt;Car Model&lt;/li&gt;
&lt;li&gt;Vehicle Type&lt;/li&gt;
&lt;li&gt;Vehicle Year&lt;/li&gt;
&lt;li&gt;Fuel Type&lt;/li&gt;
&lt;li&gt;Transmission&lt;/li&gt;
&lt;li&gt;Colour&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Dim Customer&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%2Feo174vc2sc3i7fxo12cw.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%2Feo174vc2sc3i7fxo12cw.png" alt="customer" width="800" height="450"&gt;&lt;/a&gt;&lt;br&gt;
Contains customer-related descriptive information used to analyse customer activity and performance.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Dim Location&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%2Fx03r784wwcu9m1nsh0y2.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%2Fx03r784wwcu9m1nsh0y2.png" alt="location" width="800" height="450"&gt;&lt;/a&gt;&lt;br&gt;
Contains branch and location information used for geographical and branch-level analysis.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Dim Date&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%2Fcuz6fn76n2dngf2on80p.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%2Fcuz6fn76n2dngf2on80p.png" alt="date" width="800" height="450"&gt;&lt;/a&gt;&lt;br&gt;
The Date dimension was created from the Order Date and contains:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Date&lt;/li&gt;
&lt;li&gt;Date Key&lt;/li&gt;
&lt;li&gt;Month&lt;/li&gt;
&lt;li&gt;Year&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Relationships
&lt;/h3&gt;

&lt;p&gt;The dimension tables connect to FactSales using &lt;strong&gt;one-to-many relationships&lt;/strong&gt;, with the dimension acting as the "one" side and FactSales as the "many" side.&lt;/p&gt;

&lt;p&gt;This allows users to filter transactions by dimensions such as vehicle, customer, location, and date while keeping the model structured.&lt;/p&gt;

&lt;h3&gt;
  
  
  Why a Star Schema?
&lt;/h3&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%2Fxy4o3ml278lvh6svkcst.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%2Fxy4o3ml278lvh6svkcst.png" alt="model" width="730" height="575"&gt;&lt;/a&gt;&lt;br&gt;
The star schema provided several advantages:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Clear separation between descriptive attributes and transaction data&lt;/li&gt;
&lt;li&gt;Simpler DAX calculations&lt;/li&gt;
&lt;li&gt;Better filtering behaviour&lt;/li&gt;
&lt;li&gt;Easier report maintenance&lt;/li&gt;
&lt;li&gt;Improved analytical flexibility&lt;/li&gt;
&lt;li&gt;A structure that is easier to understand and extend&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The modelling stage therefore transformed the cleaned dataset into a structure designed specifically for business analysis rather than simply reproducing the source layout.&lt;/p&gt;

&lt;h2&gt;
  
  
  Developing DAX Measures
&lt;/h2&gt;

&lt;p&gt;With the data model in place, the next stage was developing the DAX measures required for analysis.&lt;/p&gt;

&lt;p&gt;Instead of relying only on implicit aggregations, I created dedicated measures so that the business calculations could be reused consistently across different report pages and visuals.&lt;/p&gt;

&lt;h3&gt;
  
  
  Sales and Revenue Measures
&lt;/h3&gt;

&lt;p&gt;The model includes measures for:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Total Revenue&lt;/li&gt;
&lt;li&gt;Units Sold&lt;/li&gt;
&lt;li&gt;Total Orders&lt;/li&gt;
&lt;li&gt;Average Order Value&lt;/li&gt;
&lt;li&gt;Average Selling Price&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;These measures support the high-level sales analysis as well as more detailed comparisons by vehicle, branch, location, payment method, and time.&lt;/p&gt;

&lt;h3&gt;
  
  
  Profitability Measures
&lt;/h3&gt;

&lt;p&gt;To analyse value generation, I developed measures covering:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Total Cost&lt;/li&gt;
&lt;li&gt;Gross Profit&lt;/li&gt;
&lt;li&gt;Profit Margin&lt;/li&gt;
&lt;li&gt;Profit Per Vehicle&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Looking at these measures together makes it possible to distinguish sales volume from actual financial contribution.&lt;/p&gt;

&lt;h3&gt;
  
  
  Customer and Operational Measures
&lt;/h3&gt;

&lt;p&gt;Additional measures were developed for areas such as:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Total Customers&lt;/li&gt;
&lt;li&gt;Customer activity&lt;/li&gt;
&lt;li&gt;Review counts&lt;/li&gt;
&lt;li&gt;Customer ratings&lt;/li&gt;
&lt;li&gt;Delivery fees&lt;/li&gt;
&lt;li&gt;Logistics costs&lt;/li&gt;
&lt;li&gt;Returns&lt;/li&gt;
&lt;li&gt;Cancellations&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;These measures extend the analysis beyond sales and help provide an operational and customer-experience perspective.&lt;/p&gt;

&lt;h3&gt;
  
  
  Discounts and Payment Methods
&lt;/h3&gt;

&lt;p&gt;I also created measures to support discount and payment analysis, including average discount and revenue by payment method.&lt;/p&gt;

&lt;p&gt;This allowed discounts and payment channels to be analysed alongside financial performance rather than in isolation.&lt;/p&gt;

&lt;h3&gt;
  
  
  Ranking and Time Analysis
&lt;/h3&gt;

&lt;p&gt;Ranking measures were developed to compare vehicles and branches across relevant performance indicators.&lt;/p&gt;

&lt;p&gt;Time-based measures were also used to analyse changes across years and months using the Date dimension.&lt;/p&gt;

&lt;h3&gt;
  
  
  Why Measures Instead of Only Calculated Columns?
&lt;/h3&gt;

&lt;p&gt;A key part of the DAX development process was understanding when a measure was more appropriate than a calculated column.&lt;/p&gt;

&lt;p&gt;Measures are evaluated according to the current filter context. This makes them particularly useful for dashboards where the same calculation needs to respond dynamically to slicers, page filters, and visual selections.&lt;/p&gt;

&lt;p&gt;For example, Total Revenue can automatically recalculate when a user filters the report to a particular branch, vehicle type, year, or payment method.&lt;/p&gt;

&lt;p&gt;This made the DAX layer an important part of creating an interactive analytical report rather than a collection of static calculations.&lt;/p&gt;

&lt;h2&gt;
  
  
  Designing the Power BI Dashboard and Report
&lt;/h2&gt;

&lt;p&gt;After completing the data preparation, modelling, and DAX development, I moved to the report-development stage.&lt;/p&gt;

&lt;p&gt;The main design objective was to avoid creating a collection of unrelated charts. Instead, I organized the report around different levels of business analysis, starting with an executive overview and moving into detailed investigation.&lt;/p&gt;

&lt;h2&gt;
  
  
  Executive Dashboard
&lt;/h2&gt;

&lt;p&gt;The first page was designed as the management entry point.&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%2F3trhstpb4zncc861dbaj.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%2F3trhstpb4zncc861dbaj.png" alt="dahboard" width="800" height="450"&gt;&lt;/a&gt;&lt;br&gt;
It provides a high-level view of important KPIs and business performance indicators so that a user can quickly understand the overall position before moving into deeper analysis.&lt;/p&gt;

&lt;p&gt;The dashboard focuses on measures such as:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Revenue&lt;/li&gt;
&lt;li&gt;Profitability&lt;/li&gt;
&lt;li&gt;Units&lt;/li&gt;
&lt;li&gt;Orders&lt;/li&gt;
&lt;li&gt;Customers&lt;/li&gt;
&lt;li&gt;Operational indicators&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Sales &amp;amp; Analysis
&lt;/h3&gt;

&lt;p&gt;The second page provides more detailed sales 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%2Fgmn5e3ya2ssgfw3gtim8.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%2Fgmn5e3ya2ssgfw3gtim8.png" alt="sales" width="800" height="450"&gt;&lt;/a&gt;&lt;br&gt;
Users can explore performance across dimensions such as:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Vehicle type&lt;/li&gt;
&lt;li&gt;Car make&lt;/li&gt;
&lt;li&gt;Car model&lt;/li&gt;
&lt;li&gt;Branch&lt;/li&gt;
&lt;li&gt;Location&lt;/li&gt;
&lt;li&gt;Payment method&lt;/li&gt;
&lt;li&gt;Time&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This page moves from the overall picture into the factors contributing to sales performance.&lt;/p&gt;

&lt;h3&gt;
  
  
  Profitability &amp;amp; Branches
&lt;/h3&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%2F55txsfcs76az2c6izdzo.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%2F55txsfcs76az2c6izdzo.png" alt="profitability" width="778" height="493"&gt;&lt;/a&gt;&lt;br&gt;
The profitability page focuses on financial performance and branch comparisons.&lt;/p&gt;

&lt;p&gt;The intention was to allow management to examine revenue and profitability together rather than treating sales volume as the only indicator of performance.&lt;/p&gt;

&lt;h3&gt;
  
  
  Branch Detail
&lt;/h3&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%2F1d7hl3n7pj0o4z6o239l.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%2F1d7hl3n7pj0o4z6o239l.png" alt="Branch Detail" width="773" height="525"&gt;&lt;/a&gt;&lt;br&gt;
A dedicated Branch Detail page was created to provide deeper analysis of individual branches.&lt;/p&gt;

&lt;p&gt;I also implemented &lt;strong&gt;drill-through functionality&lt;/strong&gt;, allowing users to move from broader analysis into branch-specific details.&lt;/p&gt;

&lt;p&gt;This reduced the need to place every possible detail on the main dashboard.&lt;/p&gt;

&lt;h3&gt;
  
  
  Operations &amp;amp; Customer Experience
&lt;/h3&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%2F7i5bux9hauw7j223zymd.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%2F7i5bux9hauw7j223zymd.png" alt="Operations" width="790" height="410"&gt;&lt;/a&gt;&lt;br&gt;
This page examines the operational and customer side of the business.&lt;/p&gt;

&lt;p&gt;It includes analysis related to:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Delivery&lt;/li&gt;
&lt;li&gt;Logistics&lt;/li&gt;
&lt;li&gt;Returns&lt;/li&gt;
&lt;li&gt;Cancellations&lt;/li&gt;
&lt;li&gt;Ratings&lt;/li&gt;
&lt;li&gt;Reviews&lt;/li&gt;
&lt;li&gt;Customer activity&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This provides a different perspective from purely financial reporting.&lt;/p&gt;

&lt;h3&gt;
  
  
  Investigations &amp;amp; Exceptions
&lt;/h3&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%2Fyq53dhmyyfijx4lrj64m.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%2Fyq53dhmyyfijx4lrj64m.png" alt="Investigation" width="762" height="413"&gt;&lt;/a&gt;&lt;br&gt;
The investigations page was designed to identify unusual records and business patterns that could require further attention.&lt;/p&gt;

&lt;p&gt;The purpose was not to automatically label every unusual observation as an error, but to provide a structured way of identifying areas for investigation.&lt;/p&gt;

&lt;h3&gt;
  
  
  Management Insights &amp;amp; Recommendations
&lt;/h3&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%2Fm77p94uwkiiunlvwb4l9.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%2Fm77p94uwkiiunlvwb4l9.png" alt="Management" width="764" height="410"&gt;&lt;/a&gt;&lt;br&gt;
The final page translates the analysis into management-focused insights and recommendations.&lt;/p&gt;

&lt;p&gt;This helps bridge the gap between technical Power BI analysis and practical business decision-making.&lt;/p&gt;




&lt;h2&gt;
  
  
  Interactivity and Navigation
&lt;/h2&gt;

&lt;p&gt;The report includes slicers for dimensions such as:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Year&lt;/li&gt;
&lt;li&gt;Month&lt;/li&gt;
&lt;li&gt;Branch&lt;/li&gt;
&lt;li&gt;Location&lt;/li&gt;
&lt;li&gt;Vehicle Type&lt;/li&gt;
&lt;li&gt;Car Make&lt;/li&gt;
&lt;li&gt;Car Model&lt;/li&gt;
&lt;li&gt;Payment Method&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The visuals respond to selections through Power BI's filtering and cross-filtering behaviour.&lt;/p&gt;

&lt;p&gt;The report also includes:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Page navigation&lt;/li&gt;
&lt;li&gt;Drill-through&lt;/li&gt;
&lt;li&gt;A dedicated branch tooltip&lt;/li&gt;
&lt;li&gt;Interactive visual filtering&lt;/li&gt;
&lt;li&gt;Dynamic DAX measures&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;These features allow users to explore the data rather than simply read a static report.&lt;/p&gt;

&lt;h3&gt;
  
  
  Why the Report Was Structured This Way
&lt;/h3&gt;

&lt;p&gt;The report was intentionally organised from &lt;strong&gt;summary to detail&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;A management user can begin with the Executive Dashboard, identify an area that requires attention, move to a more detailed analytical page, and then use filtering or drill-through to investigate the underlying business area.&lt;/p&gt;

&lt;p&gt;This structure helped keep the main dashboard focused while still providing access to detailed analysis.&lt;/p&gt;

&lt;h2&gt;
  
  
  Turning the Dashboard into Business Analysis
&lt;/h2&gt;

&lt;p&gt;Once the report was complete, I used the model and dashboard to investigate business questions rather than simply observing the visuals.&lt;/p&gt;

&lt;p&gt;The analysis covered several areas of the JCars Logistics business.&lt;/p&gt;

&lt;h3&gt;
  
  
  Sales Performance
&lt;/h3&gt;

&lt;p&gt;I examined how revenue, units, and orders varied across:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Time&lt;/li&gt;
&lt;li&gt;Branches&lt;/li&gt;
&lt;li&gt;Locations&lt;/li&gt;
&lt;li&gt;Vehicle types&lt;/li&gt;
&lt;li&gt;Makes and models&lt;/li&gt;
&lt;li&gt;Payment methods&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This provided a way to move from overall sales performance into the specific business dimensions contributing to it.&lt;/p&gt;

&lt;h3&gt;
  
  
  Profitability
&lt;/h3&gt;

&lt;p&gt;Revenue alone does not provide a complete picture of business performance.&lt;/p&gt;

&lt;p&gt;I therefore compared revenue with costs, gross profit, profit margin, and profit per vehicle.&lt;/p&gt;

&lt;p&gt;This helped identify areas where sales activity and financial contribution needed to be considered together.&lt;/p&gt;

&lt;h3&gt;
  
  
  Vehicle Performance
&lt;/h3&gt;

&lt;p&gt;Vehicle-level analysis was used to investigate whether different vehicle types, makes, models, and vehicle characteristics showed different levels of sales and profitability.&lt;/p&gt;

&lt;p&gt;This can support future decisions around inventory, sales focus, and vehicle-level performance monitoring.&lt;/p&gt;

&lt;h3&gt;
  
  
  Branch and Location Performance
&lt;/h3&gt;

&lt;p&gt;Branch analysis was used to compare performance across locations.&lt;/p&gt;

&lt;p&gt;Rather than relying on a single KPI, I considered multiple measures including revenue, profitability, units, and operational indicators.&lt;/p&gt;

&lt;p&gt;This provides a more complete view of branch performance.&lt;/p&gt;

&lt;h3&gt;
  
  
  Customer and Operational Experience
&lt;/h3&gt;

&lt;p&gt;Customer ratings, reviews, returns, cancellations, delivery, and logistics were analysed alongside financial measures.&lt;/p&gt;

&lt;p&gt;This allowed operational performance to be considered as part of the overall business picture.&lt;/p&gt;

&lt;h3&gt;
  
  
  Discounts and Payment Methods
&lt;/h3&gt;

&lt;p&gt;Discounts were investigated in relation to sales and profitability, while payment methods were analysed based on their contribution to revenue.&lt;/p&gt;

&lt;p&gt;This helped answer questions about how different commercial activities relate to financial performance.&lt;/p&gt;

&lt;h3&gt;
  
  
  Investigating Exceptions
&lt;/h3&gt;

&lt;p&gt;The report also included an investigation section for unusual records and patterns.&lt;/p&gt;

&lt;p&gt;An important principle was to distinguish between:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;“This record is unusual”&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;and:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;“This record is definitely wrong.” **&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;An unusual observation may represent a genuine transaction, a data-quality issue, or a business situation requiring further investigation. The dashboard therefore supports investigation rather than making unsupported causal claims.&lt;/p&gt;




&lt;h2&gt;
  
  
  Analyst-Defined Questions
&lt;/h2&gt;

&lt;p&gt;In addition to the required management questions, I developed additional questions to explore the dataset more deeply.&lt;/p&gt;

&lt;p&gt;These included:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Which vehicle categories contribute to sales activity?&lt;/li&gt;
&lt;li&gt;How does profitability vary across branches?&lt;/li&gt;
&lt;li&gt;How do discounts relate to profitability?&lt;/li&gt;
&lt;li&gt;How does revenue vary by payment method?&lt;/li&gt;
&lt;li&gt;Which vehicle-level patterns require further investigation?&lt;/li&gt;
&lt;li&gt;What operational patterns exist around returns and cancellations?&lt;/li&gt;
&lt;li&gt;How does customer activity relate to business performance?&lt;/li&gt;
&lt;li&gt;How does performance change over time?&lt;/li&gt;
&lt;li&gt;Which unusual records deserve additional investigation?&lt;/li&gt;
&lt;li&gt;Which combinations of business dimensions reveal useful performance differences?&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;These questions helped guide the choice of measures and visuals instead of creating visuals first and searching for a purpose afterwards.&lt;/p&gt;

&lt;h2&gt;
  
  
  Key Insights from the Analysis
&lt;/h2&gt;

&lt;p&gt;The completed analysis produced several important observations about how the JCars Logistics business can be evaluated.&lt;/p&gt;

&lt;h3&gt;
  
  
  1. Revenue and Profitability Need to Be Viewed Together
&lt;/h3&gt;

&lt;p&gt;One of the main analytical lessons was that revenue should not be treated as the only measure of performance.&lt;/p&gt;

&lt;p&gt;A business area can generate substantial sales activity while having a different profitability profile after considering costs, discounts, delivery, and logistics.&lt;/p&gt;

&lt;p&gt;For this reason, the report presents revenue alongside gross profit, profit margin, and profit per vehicle.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. Branch Performance Requires Multiple KPIs
&lt;/h3&gt;

&lt;p&gt;Branch performance cannot be fully understood from a single metric.&lt;/p&gt;

&lt;p&gt;The report allows branch performance to be examined using financial, sales, customer, and operational indicators.&lt;/p&gt;

&lt;p&gt;This makes it possible to identify areas where a branch may require further investigation without relying on one isolated KPI.&lt;/p&gt;

&lt;h3&gt;
  
  
  3. Vehicle-Level Analysis Adds Important Detail
&lt;/h3&gt;

&lt;p&gt;Overall sales figures can hide differences between individual vehicle categories, makes, and models.&lt;/p&gt;

&lt;p&gt;Vehicle-level analysis therefore provides additional context for understanding which parts of the vehicle portfolio are contributing to sales and profitability.&lt;/p&gt;

&lt;h3&gt;
  
  
  4. Discounts Can Affect Value Generation
&lt;/h3&gt;

&lt;p&gt;Discounts are useful commercial tools, but their impact needs to be considered alongside profitability.&lt;/p&gt;

&lt;p&gt;The report therefore allows discount levels to be analysed together with financial performance instead of evaluating discounts based only on transaction activity.&lt;/p&gt;

&lt;h3&gt;
  
  
  5. Operational Metrics Add Context to Financial Results
&lt;/h3&gt;

&lt;p&gt;Delivery fees, logistics costs, returns, cancellations, ratings, and reviews provide additional information about business operations and customer experience.&lt;/p&gt;

&lt;p&gt;These indicators can help management identify areas that deserve further investigation even when the financial results alone do not immediately reveal the issue.&lt;/p&gt;

&lt;h3&gt;
  
  
  6. Data Quality Is Part of the Analysis
&lt;/h3&gt;

&lt;p&gt;The project demonstrated that data quality is not a separate activity that ends before analysis begins.&lt;/p&gt;

&lt;p&gt;Currency inconsistencies, missing values, invalid dates, inconsistent formats, and unusual records can directly affect KPIs and business conclusions.&lt;/p&gt;

&lt;p&gt;The cleaning and validation stages were therefore essential to the reliability of the final report.&lt;/p&gt;

&lt;h3&gt;
  
  
  7. Exceptions Should Be Investigated, Not Automatically Removed
&lt;/h3&gt;

&lt;p&gt;Some records appeared unusual and required additional attention.&lt;/p&gt;

&lt;p&gt;Rather than automatically deleting unusual observations, the analytical approach was to investigate them within the context of the available data.&lt;/p&gt;

&lt;p&gt;This reduces the risk of removing legitimate business activity simply because it differs from the expected pattern.&lt;/p&gt;

&lt;h2&gt;
  
  
  Management Recommendations
&lt;/h2&gt;

&lt;p&gt;The analysis provides several areas where management can use the Power BI solution to support ongoing decision-making.&lt;/p&gt;

&lt;h3&gt;
  
  
  1. Monitor Profitability Alongside Sales
&lt;/h3&gt;

&lt;p&gt;Management should review revenue together with total cost, gross profit, profit margin, and profit per vehicle.&lt;/p&gt;

&lt;p&gt;This provides a more complete picture of value generation than monitoring sales volume alone.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. Review Branch Performance Regularly
&lt;/h3&gt;

&lt;p&gt;Branch performance should be monitored using multiple KPIs rather than a single measure.&lt;/p&gt;

&lt;p&gt;Regular review of revenue, profitability, sales activity, and operational indicators can help identify branches or locations that require additional investigation.&lt;/p&gt;

&lt;h3&gt;
  
  
  3. Evaluate the Effectiveness of Discounts
&lt;/h3&gt;

&lt;p&gt;Discounts should be evaluated alongside profitability and sales performance.&lt;/p&gt;

&lt;p&gt;This can help management understand whether discounting is supporting the desired business outcome while maintaining an appropriate level of value generation.&lt;/p&gt;

&lt;h3&gt;
  
  
  4. Investigate Operational Exceptions
&lt;/h3&gt;

&lt;p&gt;Returns, cancellations, delivery-related measures, logistics costs, and unusual records should be monitored regularly.&lt;/p&gt;

&lt;p&gt;Where unexpected patterns appear, management can investigate the underlying transactions or processes before deciding on corrective action.&lt;/p&gt;

&lt;h3&gt;
  
  
  5. Use Vehicle-Level Analysis for Planning
&lt;/h3&gt;

&lt;p&gt;Vehicle make, model, type, year, and related performance indicators can provide useful information for sales and inventory planning.&lt;/p&gt;

&lt;p&gt;The dashboard can be used to identify changing performance patterns and support future vehicle-related decisions.&lt;/p&gt;

&lt;h3&gt;
  
  
  6. Maintain Data-Quality Controls
&lt;/h3&gt;

&lt;p&gt;The project showed how inconsistent source data can affect downstream analysis.&lt;/p&gt;

&lt;p&gt;Future data collection processes should therefore include validation rules for:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Currency&lt;/li&gt;
&lt;li&gt;Dates&lt;/li&gt;
&lt;li&gt;Numerical values&lt;/li&gt;
&lt;li&gt;Ratings&lt;/li&gt;
&lt;li&gt;Discounts&lt;/li&gt;
&lt;li&gt;Missing fields&lt;/li&gt;
&lt;li&gt;Categorical values&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Improving data quality at the source can reduce the amount of corrective work required during later BI development.&lt;/p&gt;




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

&lt;p&gt;The JCars Logistics project provided an end-to-end experience of developing a Business Intelligence solution from a raw dataset.&lt;/p&gt;

&lt;p&gt;The project began with data-quality investigation and progressed through Power Query cleaning, currency handling, assumptions, data modelling, DAX development, dashboard design, analysis, and management recommendations.&lt;/p&gt;

&lt;p&gt;The most important lesson was that successful BI development is not only about producing a dashboard. It is about creating a reliable chain from &lt;strong&gt;raw data to evidence-based business understanding&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;The final Power BI solution combines an analytical star schema, reusable DAX measures, interactive report pages, drill-through functionality, tooltips, and investigation-focused analysis.&lt;/p&gt;

&lt;p&gt;By combining technical data preparation with business-oriented analysis, the project demonstrates how Power BI can transform raw operational data into a practical decision-support tool.&lt;br&gt;
&lt;a href="https://dev.tourl"&gt;https://github.com/4688-tori/Jcars-Logistics-PowerBI&lt;/a&gt;&lt;/p&gt;

</description>
      <category>powerfuldevs</category>
      <category>datascience</category>
      <category>dataengineering</category>
    </item>
    <item>
      <title>Data Modelling, Relationships &amp; Joins</title>
      <dc:creator>Victoria Ndei</dc:creator>
      <pubDate>Mon, 14 Sep 2026 07:49:58 +0000</pubDate>
      <link>https://dev.to/victoria_ndei_07224d0b8db/data-modelling-relationships-joins-45cb</link>
      <guid>https://dev.to/victoria_ndei_07224d0b8db/data-modelling-relationships-joins-45cb</guid>
      <description>&lt;h2&gt;
  
  
  Introduction
&lt;/h2&gt;

&lt;p&gt;Power BI helps organizations turn raw data into meaningful insights for better decision-making. A well-designed data model is essential for accurate analysis, efficient DAX calculations, and good report performance. This article explains &lt;strong&gt;data modelling, fact and dimension tables, relationships, filter directions, and Power Query joins&lt;/strong&gt;, using practical examples to demonstrate how these concepts are applied when building an effective Power BI solution.&lt;/p&gt;

&lt;h2&gt;
  
  
  1. Data Modelling
&lt;/h2&gt;

&lt;h3&gt;
  
  
  What is data modelling?
&lt;/h3&gt;

&lt;p&gt;Data modelling in Power BI is the process of organizing data from one or more sources into tables and defining the relationships between those tables so that the data can be analyzed efficiently.&lt;/p&gt;

&lt;p&gt;A well-designed data model provides the foundation for accurate Power BI reports and dashboards. Instead of keeping all information in one large table, you can separate related information into logical tables and connect them using keys.&lt;/p&gt;

&lt;p&gt;For example, in an agricultural business, crop production records could be stored in a central fact table while information about farmers, crops, locations, and seasons is stored in separate dimension tables.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A good data model is important because it:&lt;/li&gt;
&lt;li&gt;Improves reporting and analytical accuracy.&lt;/li&gt;
&lt;li&gt;Makes DAX calculations easier to write and understand.&lt;/li&gt;
&lt;li&gt;Improves Power BI performance by reducing unnecessary data duplication.&lt;/li&gt;
&lt;li&gt;Makes models easier to scale when new data is added.&lt;/li&gt;
&lt;li&gt;Improves maintainability because each table has a clear purpose.&lt;/li&gt;
&lt;li&gt;Makes relationships and filter propagation easier to understand.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;There are three common approaches to structuring data: &lt;strong&gt;Flat Table, Star Schema, and Snowflake Schema&lt;/strong&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  1.2 Flat Table
&lt;/h3&gt;

&lt;p&gt;A flat table stores all information in a single table. Instead of separating customers, products, locations, and transactions into different tables, all the attributes and business-event information are stored together.&lt;/p&gt;

&lt;p&gt;Structure&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%2F4awambfd5kv2jeca5qfb.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%2F4awambfd5kv2jeca5qfb.png" alt="flat table" width="395" height="283"&gt;&lt;/a&gt;&lt;br&gt;
For example, an agricultural dataset could look like this:&lt;/p&gt;

&lt;p&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%2Fufwq65g2xbw9g4ukdgb8.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%2Fufwq65g2xbw9g4ukdgb8.png" alt="flat table" width="800" height="171"&gt;&lt;/a&gt;&lt;br&gt;
Illustration&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%2Fuf2oujvh22xp49lrw0ud.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%2Fuf2oujvh22xp49lrw0ud.png" alt="flat table" width="800" height="400"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;Figure 1.1- shows a flat table&lt;/em&gt;&lt;/p&gt;

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

&lt;ul&gt;
&lt;li&gt;Simple to understand.&lt;/li&gt;
&lt;li&gt;Easy to import and begin analyzing.&lt;/li&gt;
&lt;li&gt;Requires few or no relationships.&lt;/li&gt;
&lt;li&gt;Suitable for small datasets and simple reports.&lt;/li&gt;
&lt;/ul&gt;

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

&lt;ul&gt;
&lt;li&gt;Creates data redundancy because descriptive information is repeated.&lt;/li&gt;
&lt;li&gt;Can make the dataset unnecessarily large.&lt;/li&gt;
&lt;li&gt;Changes to descriptive information may need to be repeated across many rows.&lt;/li&gt;
&lt;li&gt;Can make complex DAX calculations and modelling more difficult.&lt;/li&gt;
&lt;li&gt;Less scalable as the dataset grows.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;When is it appropriate?&lt;/strong&gt;&lt;br&gt;
A flat table can be appropriate when working with a small, simple dataset, especially for basic analysis where the data does not need to be reused across multiple business processes.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Power BI implications&lt;/strong&gt;&lt;br&gt;
Flat tables are initially simple, but large flat tables can contain substantial redundancy. This can increase model size and make the model harder to maintain as more business requirements are introduced.&lt;/p&gt;
&lt;h3&gt;
  
  
  1.3 Star Schema
&lt;/h3&gt;

&lt;p&gt;A star schema separates the data into a central fact table and several surrounding dimension tables.&lt;br&gt;
The fact table contains measurable business events, while dimension tables contain descriptive information used to analyze those events.&lt;/p&gt;

&lt;p&gt;Structure&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%2F2pm2lq2i3uenwi6523u3.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%2F2pm2lq2i3uenwi6523u3.png" alt="star schema" width="795" height="567"&gt;&lt;/a&gt;&lt;br&gt;
For the agricultural example:&lt;br&gt;
&lt;strong&gt;Advantages&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Excellent for Power BI reporting and dashboards.&lt;/li&gt;
&lt;li&gt;Reduces data redundancy.&lt;/li&gt;
&lt;li&gt;Makes DAX measures easier to understand.&lt;/li&gt;
&lt;li&gt;Relationships are generally straightforward.&lt;/li&gt;
&lt;li&gt;Supports efficient filter propagation.&lt;/li&gt;
&lt;li&gt;Easier to maintain and extend.&lt;/li&gt;
&lt;li&gt;Provides a clear and intuitive model structure.&lt;/li&gt;
&lt;/ul&gt;

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

&lt;ul&gt;
&lt;li&gt;Requires more planning than a flat table.&lt;/li&gt;
&lt;li&gt;Requires relationships between tables.&lt;/li&gt;
&lt;li&gt;Users need to understand facts, dimensions, and keys.&lt;/li&gt;
&lt;li&gt;Creating the model can initially take more time.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;When is it appropriate?&lt;/strong&gt;&lt;br&gt;
Star schemas are particularly appropriate for business intelligence, reporting, and analytical systems where users need to analyze measures by different dimensions.&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;Total Profit by Crop, County, Farmer, and Season

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;can be easily analyzed using a star schema.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Power BI implications&lt;/strong&gt;&lt;br&gt;
Star schemas are generally preferred in Power BI because they provide simple relationship paths, efficient filtering, and a clear separation between descriptive attributes and measurable events.&lt;/p&gt;
&lt;h3&gt;
  
  
  1.4 Snowflake Schema
&lt;/h3&gt;

&lt;p&gt;A snowflake schema is an extension of the star schema in which dimension tables are further divided into related tables.&lt;br&gt;
For example, instead of storing the county and region directly inside DimLocation, location information could be separated:&lt;br&gt;
&lt;strong&gt;Advantages&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Reduces repeated information within dimensions.&lt;/li&gt;
&lt;li&gt;Can be useful when dimensions contain complex hierarchies.&lt;/li&gt;
&lt;li&gt;Can provide greater control over highly structured data.&lt;/li&gt;
&lt;li&gt;May reduce redundancy in some large and complex datasets.&lt;/li&gt;
&lt;/ul&gt;

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

&lt;ul&gt;
&lt;li&gt;Creates more tables.&lt;/li&gt;
&lt;li&gt;Creates more relationships.&lt;/li&gt;
&lt;li&gt;Makes filter propagation more complicated.&lt;/li&gt;
&lt;li&gt;Can make DAX and report development less intuitive.&lt;/li&gt;
&lt;li&gt;Increases model complexity.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;When is it appropriate?&lt;/strong&gt;&lt;br&gt;
A snowflake schema may be appropriate when dimensions are large, hierarchical, or naturally normalized.&lt;/p&gt;

&lt;p&gt;For example, a multinational agricultural organization could have:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Country → Region → County → Location

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;and maintaining these levels separately may be useful.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Power BI implications&lt;/strong&gt;&lt;br&gt;
Although a snowflake schema can reduce redundancy, it introduces additional relationships and filter paths. For many Power BI analytical models, a well-designed star schema is simpler and easier to work with.&lt;/p&gt;
&lt;h2&gt;
  
  
  2 Fact Table and Dimensions
&lt;/h2&gt;

&lt;p&gt;A well-designed Power BI data model usually separates business events from the descriptive information used to analyze those events. This is where fact tables and dimension tables become important.&lt;/p&gt;
&lt;h3&gt;
  
  
  Fact Tables
&lt;/h3&gt;

&lt;p&gt;A fact table stores measurable business events or transactions. It normally contains numeric values that can be aggregated, together with foreign keys that connect the facts to descriptive dimension tables.&lt;br&gt;
The table contains business events such as sales transactions. Quantity and &lt;code&gt;SalesAmount&lt;/code&gt; are measures that can be aggregated, while &lt;code&gt;CustomerID&lt;/code&gt;, &lt;code&gt;ProductID&lt;/code&gt;, and &lt;code&gt;DateID&lt;/code&gt; identify the dimensions associated with each sale.&lt;/p&gt;
&lt;h3&gt;
  
  
  Dimension Tables
&lt;/h3&gt;

&lt;p&gt;A dimension table contains descriptive information about the entities involved in business events. Dimensions provide the context used to analyze facts.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Examples include&lt;/strong&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;DimCustomer&lt;/code&gt; – customer name, gender, segment, and location.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;DimProduct&lt;/code&gt; – product name, category, brand, and price.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;DimDate&lt;/code&gt; – date, month, quarter, and year.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;DimLocation&lt;/code&gt; – county, region, town, or country.
For example:
&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%2Fqpbo554gt9ojfomlxlg5.png" alt="dimension table" width="730" height="145"&gt;
The distinction can therefore be summarized as:&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%2Fzk91cduqazuog64gugt6.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%2Fzk91cduqazuog64gugt6.png" alt="Summary" width="765" height="200"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h2&gt;
  
  
  Understanding Grain
&lt;/h2&gt;

&lt;p&gt;One of the most important concepts when designing a fact table is its grain, or granularity.&lt;br&gt;
The grain defines exactly what one row in the fact table represents.&lt;br&gt;
For example, if the grain of FactSales is:&lt;br&gt;
&lt;code&gt;"One row represents one product sold in one sales transaction."&lt;/code&gt;&lt;br&gt;
then every row should represent one product line within a transaction.&lt;br&gt;
Clearly defining the grain prevents problems such as duplicated transactions and incorrect calculations.&lt;/p&gt;
&lt;h3&gt;
  
  
  3. Relationships in Power BI
&lt;/h3&gt;

&lt;p&gt;A relationship connects tables in a Power BI data model. Relationships are necessary because business data is often distributed across multiple tables rather than stored in one large table.&lt;br&gt;
For example, &lt;code&gt;FactSales&lt;/code&gt; may contain &lt;code&gt;CustomerID&lt;/code&gt;, while &lt;code&gt;DimCustomer&lt;/code&gt; stores customer names and customer segments.&lt;br&gt;
This relationship lets Power BI connect the two tables and use customer information when analyzing sales.&lt;/p&gt;
&lt;h3&gt;
  
  
  Primary Keys and Foreign Keys
&lt;/h3&gt;

&lt;p&gt;A primary key uniquely identifies a record in a table.&lt;br&gt;
For example, &lt;code&gt;CustomerID&lt;/code&gt; can be the primary key in &lt;code&gt;DimCustomer&lt;/code&gt;:&lt;br&gt;
Each &lt;code&gt;CustomerID&lt;/code&gt; should occur only once.&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%2F20ajfab1ze5udct1iqlx.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%2F20ajfab1ze5udct1iqlx.png" alt="primary key" width="744" height="145"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The same &lt;code&gt;CustomerID&lt;/code&gt; can appear multiple times in &lt;code&gt;FactSales&lt;/code&gt; because one customer can make many purchases:&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%2Fm9497t0o9rddz3jprs6j.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%2Fm9497t0o9rddz3jprs6j.png" alt="foreign key" width="727" height="152"&gt;&lt;/a&gt;&lt;br&gt;
Each &lt;code&gt;CustomerID&lt;/code&gt; should occur only once.&lt;br&gt;
A one-to-many relationship means one record in one table can relate to many records in another table.&lt;br&gt;
For example:&lt;/p&gt;

&lt;p&gt;|DimCustomer |                |FactSales|&lt;/p&gt;

&lt;p&gt;|CustomerID   |               | CustomerID|&lt;br&gt;
    C001                        C001&lt;br&gt;
                                C001&lt;br&gt;
                               C001&lt;/p&gt;

&lt;p&gt;DimCustomer[CustomerID] contains unique values, while FactSales[CustomerID] can contain repeated values.&lt;/p&gt;

&lt;p&gt;This is the most common relationship type in a star schema.&lt;br&gt;
Use it when a dimension contains unique entities and a fact table contains multiple business events associated with those entities.&lt;/p&gt;
&lt;h3&gt;
  
  
  3.2 One-to-One Relationship (1:1)
&lt;/h3&gt;

&lt;p&gt;A one-to-one relationship means that each record in one table corresponds to only one record in another table.&lt;br&gt;
For example:&lt;br&gt;
EmployeeID     EmployeeDetails&lt;br&gt;
E001     ────&amp;gt; E001&lt;br&gt;
E002     ────&amp;gt; E002&lt;br&gt;
E003     ────&amp;gt; E003&lt;/p&gt;

&lt;p&gt;This relationship may be appropriate when information about the same entity has intentionally been split into two tables.&lt;br&gt;
However, a 1:1 relationship should not normally be used simply because two tables can be connected by a column. If the tables represent the same entity and there is no strong modelling reason to separate them, combining them may be simpler.&lt;/p&gt;
&lt;h3&gt;
  
  
  3.3 Many-to-Many Relationship (:)
&lt;/h3&gt;

&lt;p&gt;A many-to-many relationship occurs when multiple records in one table can be related to multiple records in another table.&lt;br&gt;
For example, students can enrol in multiple courses, while each course can have multiple students.&lt;/p&gt;

&lt;p&gt;Students                    Courses&lt;/p&gt;

&lt;p&gt;Student A  ───────────────&amp;gt; Course 1&lt;br&gt;
Student A  ───────────────&amp;gt; Course 2&lt;br&gt;
Student B  ───────────────&amp;gt; Course 1&lt;br&gt;
Student C  ───────────────&amp;gt; Course 2&lt;/p&gt;

&lt;p&gt;In a proper dimensional model, a bridge or factless fact table can often be introduced to resolve this situation.&lt;br&gt;
Many-to-many relationships should be used carefully because they can make filter&lt;br&gt;
propagation and DAX calculations more difficult to understand.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Cardinality&lt;/strong&gt;&lt;br&gt;
Cardinality describes how records in two related tables correspond to each other.&lt;br&gt;
The main cardinalities in Power BI are:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;1:* — One-to-Many&lt;/li&gt;
&lt;li&gt;1:1 — One-to-One&lt;/li&gt;
&lt;li&gt;
&lt;em&gt;:&lt;/em&gt; — Many-to-Many
For most analytical models, 1:* relationships are preferred because they provide a clear dimension-to-fact structure.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Referential Integrity&lt;/strong&gt;&lt;br&gt;
Referential integrity means that foreign-key values should correspond to valid primary-key values in the related table.&lt;br&gt;
For example, if &lt;code&gt;FactSales&lt;/code&gt; contains:&lt;/p&gt;

&lt;p&gt;CustomerID = C005&lt;/p&gt;

&lt;p&gt;then C005 should normally exist in DimCustomer.&lt;br&gt;
Missing dimension records can create unmatched or blank members in reports and may indicate data-quality problems.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Active and Inactive Relationships&lt;/strong&gt;&lt;br&gt;
Power BI can have both active and inactive relationships.&lt;br&gt;
An active relationship is used automatically when filters move between related tables.&lt;br&gt;
An inactive relationship is not automatically used but can be activated in a DAX calculation when required.&lt;br&gt;
A common example is a sales table containing multiple date columns:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;FactSales
---------
OrderDate
ShipDate
DeliveryDate
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A date dimension may therefore have multiple relationships to the fact table. Only one can normally be active for the same pair of tables, while others can remain inactive and be activated in specific calculations.&lt;/p&gt;

&lt;h2&gt;
  
  
  4. Filter Direction
&lt;/h2&gt;

&lt;p&gt;Relationships do more than connect tables. They also determine how filters propagate through the model.&lt;/p&gt;

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

&lt;p&gt;In a typical star schema, filtering flows from the dimension table to the fact table.&lt;br&gt;
For example:&lt;/p&gt;

&lt;p&gt;DimProduct&lt;br&gt;
    │&lt;br&gt;
    │ Filter&lt;br&gt;
    ▼&lt;br&gt;
FactSales&lt;/p&gt;

&lt;p&gt;Suppose DimProduct contains:&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%2Fzo36xir10wtm2kiv5dph.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%2Fzo36xir10wtm2kiv5dph.png" alt="single filter" width="589" height="115"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;If a user selects Electronics in a Power BI report, the filter is passed from &lt;code&gt;DimProduct to FactSales.&lt;/code&gt;&lt;br&gt;
Power BI then calculates sales using only the transactions associated with products in the Electronics category.&lt;br&gt;
This is the preferred direction for many star-schema models because it is predictable and easy to understand.&lt;/p&gt;
&lt;h4&gt;
  
  
  Bidirectional Filtering
&lt;/h4&gt;

&lt;p&gt;With bidirectional filtering, filters can move in both directions:&lt;br&gt;
DimProduct&lt;br&gt;
    ↕&lt;br&gt;
FactSales&lt;/p&gt;

&lt;p&gt;Although bidirectional filtering can solve some modelling scenarios, it should be used carefully.&lt;br&gt;
It can create:&lt;/p&gt;
&lt;h4&gt;
  
  
  Ambiguous filter paths
&lt;/h4&gt;

&lt;ul&gt;
&lt;li&gt;Unexpected filtering behaviour&lt;/li&gt;
&lt;li&gt;More complex DAX calculations&lt;/li&gt;
&lt;li&gt;Performance problems in complex models&lt;/li&gt;
&lt;li&gt;Difficulty understanding why a visual is being filtered&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;For example, if several dimension tables can reach each other through multiple paths, Power BI may have more than one possible route through which a filter can travel.&lt;br&gt;
Therefore, single-direction filtering from dimensions to facts is generally preferable in a standard star schema.&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%2Fblkdkjhagp3acix9tq7x.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%2Fblkdkjhagp3acix9tq7x.png" alt="filter propogation" width="573" height="210"&gt;&lt;/a&gt;&lt;br&gt;
_Figure 4.1 single-direction filtering versus bidirectional filtering between DimProduct and FactSales _&lt;/p&gt;
&lt;h2&gt;
  
  
  5. Joins in Power Query
&lt;/h2&gt;

&lt;p&gt;Relationships are not the only way to work with multiple tables. Power Query provides Merge Queries, which allow tables to be joined during the data transformation stage.&lt;br&gt;
A join matches records from two tables using one or more common columns.&lt;br&gt;
Consider these two 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%2F8822pz10m34az15rvcb1.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%2F8822pz10m34az15rvcb1.png" alt="customers" width="637" height="113"&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%2Fm4znew0w8fgr0004ci2g.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%2Fm4znew0w8fgr0004ci2g.png" alt="orders" width="568" height="177"&gt;&lt;/a&gt;&lt;br&gt;
The tables can be merged using CustomerID.&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%2Fdgfd5toxymt2v75d807p.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%2Fdgfd5toxymt2v75d807p.png" alt="join types" width="590" height="152"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h3&gt;
  
  
  5.1 Left Outer Join
&lt;/h3&gt;

&lt;p&gt;A Left Outer Join keeps every record from the left table and matching records from the right table.&lt;br&gt;
If Customers is the left 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%2Fwil74tre3irvbvjvu63z.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%2Fwil74tre3irvbvjvu63z.png" alt="left outer join" width="590" height="152"&gt;&lt;/a&gt;&lt;br&gt;
Carol remains in the result even though she has no matching order.&lt;br&gt;
Use case: Find all customers and show their orders where available.&lt;/p&gt;
&lt;h2&gt;
  
  
  5.2 Right Outer Join
&lt;/h2&gt;

&lt;p&gt;A Right Outer Join keeps every record from the right table and matching records from the left 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%2Ft56eq95xfrsze6iilhof.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%2Ft56eq95xfrsze6iilhof.png" alt="right outer join" width="576" height="141"&gt;&lt;/a&gt;&lt;br&gt;
Order O004 remains even though customer C004 does not exist in the Customers table.&lt;br&gt;
Use case: Keep all orders and add customer information when available.&lt;/p&gt;
&lt;h3&gt;
  
  
  5.3 Full Outer Join
&lt;/h3&gt;

&lt;p&gt;A Full Outer Join keeps all records from both tables, whether or not they have a match.&lt;br&gt;
The result would include:&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%2Fxp82vojqyrvtrzca0orx.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%2Fxp82vojqyrvtrzca0orx.png" alt="full outer join" width="575" height="141"&gt;&lt;/a&gt;&lt;br&gt;
This is useful when you need to identify both:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Customers without orders&lt;/li&gt;
&lt;li&gt;Orders without valid customers
It can therefore be particularly useful for data-quality analysis.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;
  
  
  5.4 Inner Join
&lt;/h2&gt;

&lt;p&gt;An Inner Join keeps only records that exist in both tables.&lt;br&gt;
Customers without orders and orders without matching customers are excluded.&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%2Fc90vtk7677ct9kcv9ii3.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%2Fc90vtk7677ct9kcv9ii3.png" alt="Inner join" width="569" height="118"&gt;&lt;/a&gt;&lt;br&gt;
Use case: Analyse only orders associated with valid customers.&lt;/p&gt;
&lt;h3&gt;
  
  
  5.5 Left Anti Join
&lt;/h3&gt;

&lt;p&gt;A Left Anti Join returns records that exist in the left table but have no matching record in the right table.&lt;br&gt;
Using Customers as the left 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%2Fgtofakf7h8j2409aoldv.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%2Fgtofakf7h8j2409aoldv.png" alt="left anti join" width="563" height="57"&gt;&lt;/a&gt;&lt;br&gt;
This identifies customers who have not placed an order.&lt;br&gt;
Use case: Finding inactive customers or records missing from another dataset.&lt;/p&gt;
&lt;h3&gt;
  
  
  5.6 Right Anti Join
&lt;/h3&gt;

&lt;p&gt;A Right Anti Join returns records that exist in the right table but have no matching record in the left table.&lt;br&gt;
Using Orders as the 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%2F9pn1lo1qfrq6a9hqtv4q.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%2F9pn1lo1qfrq6a9hqtv4q.png" alt="right anti join" width="568" height="55"&gt;&lt;/a&gt;&lt;br&gt;
This identifies orders whose customer does not exist in the Customers table.&lt;br&gt;
Use case: Detecting invalid foreign keys or data-quality issues&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%2F8e99hflgttlv626dtl87.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%2F8e99hflgttlv626dtl87.png" alt="joint summary" width="737" height="253"&gt;&lt;/a&gt;&lt;br&gt;
_ Figure 5.3 -illustrates the summary of different joins _&lt;/p&gt;
&lt;h2&gt;
  
  
  6. Power Query Joins vs Power BI Relationships
&lt;/h2&gt;

&lt;p&gt;Although both joins and relationships connect tables, they serve different purposes and operate at different stages of the Power BI workflow.&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%2Fl0vm05ro0vrehz5a53fb.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%2Fl0vm05ro0vrehz5a53fb.png" alt="power  query vs power bi" width="666" height="229"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h3&gt;
  
  
  Power Query Merge
&lt;/h3&gt;

&lt;p&gt;A Merge Query is a data-transformation operation.&lt;br&gt;
It occurs before the data is loaded into the Power BI model.&lt;br&gt;
For example:&lt;br&gt;
Customers&lt;br&gt;
   +&lt;br&gt;
Orders&lt;br&gt;
   ↓&lt;br&gt;
Power Query Merge&lt;br&gt;
   ↓&lt;br&gt;
Combined Table&lt;/p&gt;

&lt;p&gt;The merge can bring columns from one table into another.&lt;br&gt;
For example, an Orders table could be merged with Customers to add:&lt;br&gt;
&lt;code&gt;CustomerName&lt;br&gt;
CustomerSegment&lt;br&gt;
County&lt;/code&gt;&lt;br&gt;
The result is physically represented as a combined query/table when loaded into the model.&lt;/p&gt;
&lt;h3&gt;
  
  
  Power BI Relationship
&lt;/h3&gt;

&lt;p&gt;A relationship is created after the tables are loaded into the data model.&lt;br&gt;
For example:&lt;br&gt;
DimCustomer&lt;br&gt;
     │&lt;br&gt;
     │ 1:*&lt;br&gt;
     ▼&lt;br&gt;
FactSales&lt;/p&gt;

&lt;p&gt;The relationship does not physically combine the tables.&lt;br&gt;
Instead, it tells Power BI how the tables are connected so that filters and calculations can work across them.&lt;/p&gt;
&lt;h3&gt;
  
  
  Key Difference
&lt;/h3&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%2Fzw79rnbr21gefjpa4fon.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%2Fzw79rnbr21gefjpa4fon.png" alt="key differences" width="647" height="216"&gt;&lt;/a&gt;&lt;br&gt;
When Should You Use a Merge?&lt;br&gt;
Tables, Relationships, Filter Direction and Joins in Power BI&lt;br&gt;
A merge is appropriate when the columns are genuinely required in the same table.&lt;br&gt;
For example, if a source system stores product information separately but a transformation requires the product category to be included in a staging table, merging may be appropriate.&lt;/p&gt;
&lt;h3&gt;
  
  
  When Should You Use a Relationship?
&lt;/h3&gt;

&lt;p&gt;A relationship is preferable when the tables represent different business entities.&lt;/p&gt;

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

&lt;p&gt;DimProduct&lt;br&gt;
     │&lt;br&gt;
     ▼&lt;br&gt;
FactSales&lt;/p&gt;

&lt;p&gt;There is usually no need to copy product name, category, and brand into every sales transaction.&lt;br&gt;
Keeping them in DimProduct reduces data redundancy and preserves a clean dimensional model.&lt;/p&gt;
&lt;h3&gt;
  
  
  Problems with Excessive Merging
&lt;/h3&gt;

&lt;p&gt;Excessive merging can create a very wide table containing repeated descriptive information.&lt;br&gt;
For example, instead of:&lt;br&gt;
&lt;code&gt;DimProduct&lt;br&gt;
ProductID&lt;br&gt;
ProductName&lt;br&gt;
Category&lt;br&gt;
Brand&lt;br&gt;
&lt;/code&gt;&lt;br&gt;
and:&lt;br&gt;
&lt;code&gt;&lt;br&gt;
FactSales&lt;br&gt;
SalesID&lt;br&gt;
ProductID&lt;br&gt;
Quantity&lt;br&gt;
Quantity&lt;br&gt;
SalesAmount&lt;br&gt;
&lt;/code&gt;&lt;br&gt;
a merge could produce:&lt;br&gt;
&lt;code&gt;&lt;br&gt;
SalesID&lt;br&gt;
ProductID&lt;br&gt;
ProductName&lt;br&gt;
Category&lt;br&gt;
Brand&lt;br&gt;
Quantity&lt;br&gt;
SalesAmount&lt;br&gt;
&lt;/code&gt;&lt;br&gt;
If thousands or millions of transactions contain the same product information, the data becomes unnecessarily repetitive.&lt;br&gt;
This can increase model size, reduce readability, and make maintenance more difficult.&lt;br&gt;
For business intelligence solutions, keeping fact and dimension tables separate is often preferable because it supports a cleaner and more scalable model.&lt;/p&gt;
&lt;h2&gt;
  
  
  7. Recommended Power BI Model
&lt;/h2&gt;

&lt;p&gt;For a typical business intelligence project, I would recommend a star schema rather than a flat table or a highly normalized snowflake schema.&lt;br&gt;
A typical model could look like this:&lt;br&gt;
graph TD&lt;br&gt;
`&lt;br&gt;
    Customer["DimCustomer"]&lt;br&gt;
    Product["DimProduct"]&lt;br&gt;
    Date["DimDate"]&lt;br&gt;
    Location["DimLocation"]&lt;br&gt;
    Sales["FactSales"]&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Customer --&amp;gt;|1:*| Sales
Product --&amp;gt;|1:*| Sales
Date --&amp;gt;|1:*| Sales
Location --&amp;gt;|1:*| Sales
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;
&lt;p&gt;`&lt;br&gt;
The fact table contains measurable business events, while dimensions contain descriptive attributes.&lt;br&gt;
Why Choose a Star Schema?&lt;/p&gt;
&lt;h3&gt;
  
  
  1. Performance
&lt;/h3&gt;

&lt;p&gt;Star schemas generally provide an efficient structure for analytical queries because dimensions are separated from large transaction tables and relationships are straightforward.&lt;/p&gt;
&lt;h3&gt;
  
  
  2. Simpler DAX
&lt;/h3&gt;

&lt;p&gt;Clear relationships make DAX calculations easier to understand.&lt;br&gt;
For example, a measure such as:&lt;br&gt;
&lt;code&gt;Total Sales = SUM(FactSales[SalesAmount])&lt;/code&gt;&lt;br&gt;
can be analyzed by product, customer, location, or date through the relationships in the model.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Model Readability
A star schema is visually easy to understand. Developers and report users can quickly identify:&lt;/li&gt;
&lt;li&gt;Which table contains transactions&lt;/li&gt;
&lt;li&gt;Which tables contain descriptive attributes&lt;/li&gt;
&lt;li&gt;How the tables are connected
###4. Scalability
As the business grows, additional dimensions can be added without redesigning the entire model.
For example:
&lt;/li&gt;
&lt;/ol&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;             DimCustomer
                  |
DimProduct — FactSales — DimLocation
                  |
               DimDate
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;A future DimSalesperson or DimPromotion could also be connected to the fact table.&lt;/p&gt;
&lt;h3&gt;
  
  
  5. Reduced Redundancy
&lt;/h3&gt;

&lt;p&gt;Product and customer information does not need to be repeated for every transaction.&lt;br&gt;
Instead of storing:&lt;/p&gt;

&lt;p&gt;Laptop | Electronics | HP | 500 times&lt;/p&gt;

&lt;p&gt;the information can be stored once in DimProduct and referenced through ProductID.&lt;/p&gt;
&lt;h3&gt;
  
  
  6. Maintainability
&lt;/h3&gt;

&lt;p&gt;If a product category changes, it can be updated in the dimension rather than repeatedly changing the same information across thousands of fact records.&lt;/p&gt;
&lt;h3&gt;
  
  
  7. Easier Reporting
&lt;/h3&gt;

&lt;p&gt;Report users can drag attributes such as:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;DimProduct[Category]
DimDate[Year]
DimLocation[County]
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;&lt;br&gt;
`&lt;br&gt;
and measures such as:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;[Total Sales]&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;into visualizations without needing to understand complex joins.&lt;/p&gt;

&lt;h3&gt;
  
  
  Recommended Relationships
&lt;/h3&gt;

&lt;p&gt;For a standard star schema, I would normally implement:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;One-to-many &lt;code&gt;(1:*) &lt;/code&gt;relationships&lt;/li&gt;
&lt;li&gt;Dimension table on the 1 side&lt;/li&gt;
&lt;li&gt;Fact table on the &lt;code&gt;*&lt;/code&gt;side&lt;/li&gt;
&lt;li&gt;Single-direction filtering from dimensions to facts&lt;/li&gt;
&lt;li&gt;Unique primary keys in dimension tables&lt;/li&gt;
&lt;li&gt;Matching foreign keys in fact tables&lt;/li&gt;
&lt;li&gt;Clearly defined fact-table grain&lt;/li&gt;
&lt;li&gt;Active relationships for the main analytical paths
Many-to-many relationships should only be introduced when the business requirement genuinely requires them, and bridge tables should be considered where appropriate.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Star Schema vs Flat Table vs Snowflake Schema
&lt;/h3&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%2Fysge99llhnl3egwb50bp.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%2Fysge99llhnl3egwb50bp.png" alt="differences" width="738" height="361"&gt;&lt;/a&gt;&lt;br&gt;
A flat table can be useful for small or simple datasets, but it can become inefficient and difficult to maintain as the amount of data grows.&lt;br&gt;
A snowflake schema normalizes dimensions into additional related tables. This can reduce redundancy further, but it introduces additional relationships and complexity.&lt;br&gt;
For most Power BI analytical models, the star schema provides the best balance between performance, simplicity, scalability, and maintainability.&lt;/p&gt;

&lt;h2&gt;
  
  
  Final Recommendation
&lt;/h2&gt;

&lt;p&gt;My preferred Power BI model would therefore be a star schema with a central fact table surrounded by dimension tables. I would use one-to-many relationships wherever possible, with dimensions on the 1 side and facts on the * side.&lt;br&gt;
I would also use single-direction filter propagation from dimensions to facts unless there is a specific modelling requirement for bidirectional filtering.&lt;br&gt;
This approach produces a model that is easier to understand, easier to maintain, and easier to use when developing DAX measures and reports. More importantly, it separates business events from descriptive context, allowing the model to scale as the organization's reporting requirements grow.&lt;/p&gt;

&lt;p&gt;A good Power BI model is therefore not simply about connecting tables. It is about designing those connections so that the resulting model is accurate, performant, understandable, and useful for business analysis.&lt;/p&gt;

</description>
      <category>productivity</category>
      <category>tutorial</category>
      <category>beginners</category>
    </item>
    <item>
      <title>Building an Interactive Excel Dashboard For E-commerce for Product Analysis: A Case Study for Jumia Products</title>
      <dc:creator>Victoria Ndei</dc:creator>
      <pubDate>Sun, 06 Sep 2026 19:32:17 +0000</pubDate>
      <link>https://dev.to/victoria_ndei_07224d0b8db/building-an-interactive-excel-dashboard-for-e-commerce-for-product-analysis-a-case-study-for-jumia-2k0i</link>
      <guid>https://dev.to/victoria_ndei_07224d0b8db/building-an-interactive-excel-dashboard-for-e-commerce-for-product-analysis-a-case-study-for-jumia-2k0i</guid>
      <description>&lt;h2&gt;
  
  
  Introduction
&lt;/h2&gt;

&lt;p&gt;E-commerce platforms generate large amounts of product information that can help explain pricing strategies, customer engagement, and product performance.&lt;/p&gt;

&lt;p&gt;For this project, I analyzed a dataset of products from Jumia, an e-commerce marketplace, using Microsoft Excel. The goal was to transform raw product data into a cleaned dataset, perform descriptive and relationship analysis, and develop an interactive dashboard that communicates the most important findings.&lt;br&gt;
The analysis focused on four major areas:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Product pricing&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Discounts&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Customer reviews&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Product rating&lt;br&gt;
The final result was an Excel-based analytical dashboard designed to help sellers identify high-performing products, products requiring attention, and potential pricing and marketing strategies.&lt;/p&gt;
&lt;h2&gt;
  
  
  Project Objectives
&lt;/h2&gt;

&lt;p&gt;The goal of this project is to create an interactive Excel dashboard that provides insights into the&lt;br&gt;
performance of products listed on Jumia and is capable of answering the following questions:&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;What is the average product price?&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;What is the average discount?&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Which products have the highest ratings?&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Which products have the highest number of reviews?&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Do higher discounts result in higher customer engagement?&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Are highly rated products more expensive?&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Which products are performing well?&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Which products may require improved pricing or product strategies?&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;What recommendations can be made to Jumia sellers?&lt;/p&gt;
&lt;h2&gt;
  
  
  Dataset Description
&lt;/h2&gt;

&lt;p&gt;The dataset contains information about Jumia products&lt;br&gt;
using the following fields:&lt;br&gt;
&lt;strong&gt;Product&lt;/strong&gt;:product name&lt;br&gt;
&lt;strong&gt;Current Price&lt;/strong&gt;:Current selling price&lt;br&gt;
&lt;strong&gt;Old Price&lt;/strong&gt;:Original/listed price&lt;br&gt;
&lt;strong&gt;Discount&lt;/strong&gt;:Percentage discount&lt;br&gt;
&lt;strong&gt;Review&lt;/strong&gt;:Number of customer reviews&lt;br&gt;
&lt;strong&gt;Rating&lt;/strong&gt;:Customer rating out of 5&lt;br&gt;
The original dataset contained 115 records.&lt;br&gt;
After data cleaning and duplicate removal, the final dataset contained 112 products&lt;/p&gt;
&lt;h3&gt;
  
  
  Raw Data
&lt;/h3&gt;

&lt;p&gt;The first step was to inspect the raw dataset before making any changes.&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%2Fu4y16uxtrmkpfe22e24t.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%2Fu4y16uxtrmkpfe22e24t.png" alt="Raw data" width="800" height="298"&gt;&lt;/a&gt;&lt;br&gt;
The initial audit looked for:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Missing values&lt;/li&gt;
&lt;li&gt;Duplicate records&lt;/li&gt;
&lt;li&gt;Incorrect data types&lt;/li&gt;
&lt;li&gt;Negative review counts&lt;/li&gt;
&lt;li&gt;Invalid ratings&lt;/li&gt;
&lt;li&gt;Invalid discounts&lt;/li&gt;
&lt;li&gt;Price inconsistencies&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%2Ffil09wgo11utfvk9uj9j.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%2Ffil09wgo11utfvk9uj9j.png" alt="Figure 2:Cleaned Data" width="799" height="334"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h2&gt;
  
  
  Data Cleaning
&lt;/h2&gt;

&lt;p&gt;Data cleaning was important because raw e-commerce data is not always immediately suitable for analysis.&lt;br&gt;
For my data cleaning, I used Power Query, which made the cleaning process much easier.&lt;/p&gt;
&lt;h3&gt;
  
  
  Removing Duplicate Records
&lt;/h3&gt;

&lt;p&gt;Duplicate records can cause products to be counted more than once and can distort totals and averages.&lt;br&gt;
The dataset contained three duplicate records. These duplicates were removed, leaving 112 unique records.&lt;/p&gt;
&lt;h3&gt;
  
  
  Cleaning Review Counts
&lt;/h3&gt;

&lt;p&gt;Some review values were negative.&lt;br&gt;
A review count cannot logically be negative, so these values were converted to their absolute values.&lt;br&gt;
Blank review values were retained as missing values rather than automatically being treated as zero.&lt;br&gt;
This distinction is important because a missing review count does not necessarily mean that the product received zero reviews.&lt;/p&gt;
&lt;h3&gt;
  
  
  Cleaning Ratings
&lt;/h3&gt;

&lt;p&gt;Ratings were originally stored in text formats such as:&lt;br&gt;
&lt;code&gt;4.5 out of 5&lt;/code&gt;&lt;br&gt;
These values were converted into numerical ratings so that averages, sorting, and correlations could be calculated.&lt;/p&gt;
&lt;h3&gt;
  
  
  Cleaning Discounts
&lt;/h3&gt;

&lt;p&gt;Discount values were originally represented as percentages, such as:&lt;br&gt;
&lt;code&gt;38%&lt;/code&gt;&lt;br&gt;
These were converted into numerical percentage values for analysis.&lt;/p&gt;
&lt;h3&gt;
  
  
  Cleaning Prices
&lt;/h3&gt;

&lt;p&gt;Currency text was removed from the price fields so that prices could be treated as numerical values.&lt;br&gt;
One product contained a price range rather than a single price. A midpoint was used for the numerical analysis so that the record could be included in price calculations.&lt;br&gt;
Also, we removed the 'KSh' and ',' before changing prices to whole numbers.&lt;/p&gt;
&lt;h2&gt;
  
  
  Data Enrichment
&lt;/h2&gt;

&lt;p&gt;After cleaning the original fields, additional analytical columns were created.&lt;/p&gt;
&lt;h2&gt;
  
  
  Discount Amount
&lt;/h2&gt;

&lt;p&gt;The discount amount was calculated using:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Discount Amount = Old Price - Current Price

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This provides the actual monetary reduction rather than only the percentage reduction.&lt;/p&gt;

&lt;h3&gt;
  
  
  Rating Category
&lt;/h3&gt;

&lt;p&gt;Products were categorized according to their rating performance.&lt;br&gt;
The categories were used to make it easier to analyze the overall distribution of customer ratings.&lt;/p&gt;

&lt;h3&gt;
  
  
  Discount Category
&lt;/h3&gt;

&lt;p&gt;Products were also grouped into discount categories:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Low&lt;/li&gt;
&lt;li&gt;Medium&lt;/li&gt;
&lt;li&gt;High
These categories made it possible to compare product performance across different discount levels.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Price Category
&lt;/h3&gt;

&lt;p&gt;Products were grouped into price ranges to make price analysis easier and to support interactive dashboard filtering.&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%2Fjif3yzyiuhg7rdrdoc94.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%2Fjif3yzyiuhg7rdrdoc94.png" alt="Cleaned  Data" width="799" height="310"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Excel Techniques Used
&lt;/h2&gt;

&lt;p&gt;Several Excel features were used throughout the project.&lt;/p&gt;

&lt;h3&gt;
  
  
  Excel Formulas
&lt;/h3&gt;

&lt;p&gt;Formulas were used to create calculated fields and perform summary calculations.&lt;br&gt;
For example, the discount amount was calculated by subtracting the current price from the old price.&lt;br&gt;
Functions such as &lt;code&gt;AVERAGE&lt;/code&gt;, &lt;code&gt;SUM&lt;/code&gt;, &lt;code&gt;COUNT&lt;/code&gt;, &lt;code&gt;MAX&lt;/code&gt;, &lt;code&gt;MIN&lt;/code&gt;, &lt;code&gt;IF&lt;/code&gt;, and other conditional functions were useful for transforming and analyzing the dataset.&lt;/p&gt;

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

&lt;p&gt;Sorting was used to identify:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Highest-rated products&lt;/li&gt;
&lt;li&gt;Lowest-rated products&lt;/li&gt;
&lt;li&gt;Products with the most reviews&lt;/li&gt;
&lt;li&gt;Products with the highest discounts&lt;/li&gt;
&lt;li&gt;Products requiring attention&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Pivot Tables
&lt;/h3&gt;

&lt;p&gt;Pivot Tables were used to summarize product performance by categories such as rating and discount levels.&lt;br&gt;
They provided a flexible way to aggregate the data without manually calculating every summary.&lt;/p&gt;

&lt;h3&gt;
  
  
  Pivot Charts
&lt;/h3&gt;

&lt;p&gt;Pivot Charts converted the Pivot Table results into visualizations suitable for the dashboard.&lt;/p&gt;

&lt;h3&gt;
  
  
  Slicers
&lt;/h3&gt;

&lt;p&gt;Slicers were added to make the dashboard interactive.&lt;br&gt;
Users can filter the dashboard based on variables such as:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Rating Category&lt;/li&gt;
&lt;li&gt;Discount Category&lt;/li&gt;
&lt;li&gt;Price Category&lt;/li&gt;
&lt;/ul&gt;

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

&lt;h3&gt;
  
  
  1 Descriptive Analysis
&lt;/h3&gt;

&lt;p&gt;After cleaning the data, the following statistics were obtained:&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%2Fh8815lp7in6msch9mtoz.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%2Fh8815lp7in6msch9mtoz.png" alt="Figure 3:Descriptive analysis" width="407" height="494"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Trend Analysis
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Relationship Between Discounts and Reviews
&lt;/h3&gt;

&lt;p&gt;One of the main questions was whether larger discounts result in greater customer engagement.&lt;br&gt;
Customer engagement was represented using the number of reviews.&lt;br&gt;
The correlation between discount percentage and review count was approximately:&lt;br&gt;
-0.14&lt;br&gt;
This represents a very weak negative relationship.&lt;br&gt;
Therefore, the analysis does not provide evidence that increasing the discount percentage automatically results in more customer reviews.&lt;br&gt;
Some products with substantial discounts had very few reviews, while some products with moderate discounts had significantly more reviews. &lt;/p&gt;

&lt;h3&gt;
  
  
  Business Interpretation
&lt;/h3&gt;

&lt;p&gt;Sellers should therefore avoid treating discounts as the only method of increasing engagement.&lt;br&gt;
Product quality, value, visibility, presentation, and customer experience may also influence customer response.&lt;/p&gt;

&lt;h3&gt;
  
  
  Relationship Between Price and Rating
&lt;/h3&gt;

&lt;p&gt;The correlation between current price and rating was approximately:&lt;br&gt;
+0.11&lt;br&gt;
This is a very weak positive relationship.&lt;br&gt;
The result suggests that expensive products were not necessarily rated much higher than cheaper products.&lt;br&gt;
For example, the Electronic Digital Display Vernier Caliper had a relatively low current price but achieved a rating of 4.6/5 alongside 49 reviews.&lt;/p&gt;

&lt;h3&gt;
  
  
  Business Interpretation
&lt;/h3&gt;

&lt;p&gt;Price alone does not appear to determine customer satisfaction.&lt;br&gt;
Sellers should focus on providing strong value for money rather than assuming that a higher selling price will result in higher ratings.&lt;/p&gt;

&lt;h3&gt;
  
  
  Relationship Between Ratings and Reviews
&lt;/h3&gt;

&lt;p&gt;The correlation between rating and review count was approximately:&lt;br&gt;
+0.06&lt;br&gt;
This is effectively a negligible linear relationship.&lt;br&gt;
A product can therefore have many reviews without necessarily having a high rating.&lt;br&gt;
This is particularly important when evaluating product performance.&lt;br&gt;
A high number of reviews indicates strong customer engagement, but it does not automatically indicate customer satisfaction.&lt;/p&gt;

&lt;h2&gt;
  
  
  Sales Performance Analysis
&lt;/h2&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%2Feu0e9j1hlwfsacf46yli.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%2Feu0e9j1hlwfsacf46yli.png" alt="Analysis" width="800" height="336"&gt;&lt;/a&gt;&lt;br&gt;
The following table summarizes the product performance analysis by:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Strong demand proxy: Which products have review counts above the engagement threshold?&lt;/li&gt;
&lt;li&gt;Pricing/marketing attention: Which products combine weak engagement or weak ratings with high price/high discount?&lt;/li&gt;
&lt;li&gt;Many reviews, average experience: Which products have high engagement but fall in the Average rating category?
-Promotion inefficiency: Which high-discount products remain below the engagement threshold?&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Building the Dashboard
&lt;/h2&gt;

&lt;p&gt;The final stage of the project was creating an interactive Excel dashboard.&lt;br&gt;
The dashboard was designed to provide a quick overview of product performance without requiring the user to inspect the raw dataset.&lt;/p&gt;

&lt;h3&gt;
  
  
  KPI Cards
&lt;/h3&gt;

&lt;p&gt;The dashboard includes key performance indicators such as:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Total Products&lt;/li&gt;
&lt;li&gt;Average Current Price&lt;/li&gt;
&lt;li&gt;Average Discount&lt;/li&gt;
&lt;li&gt;Average Rating&lt;/li&gt;
&lt;li&gt;Total Reviews
##Product Performance Charts
Charts were created to display:&lt;/li&gt;
&lt;li&gt;Top 10 products by rating&lt;/li&gt;
&lt;li&gt;Top 10 products by reviews&lt;/li&gt;
&lt;li&gt;Top 10 products by discount
###Category Analysis
The dashboard also presents the distribution of:&lt;/li&gt;
&lt;li&gt;Rating categories
-Discount categories
-Price categories
###Interactive Slicers
Slicers allow the user to filter the dashboard by selected product characteristics.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Screenshot: Pivot Tables&lt;/p&gt;

&lt;p&gt;Insert screenshot of your Pivot Tables here.&lt;/p&gt;

&lt;p&gt;Screenshot: Charts&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fte2otqa6utnkqy0k04al.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%2Fte2otqa6utnkqy0k04al.png" alt="Figure 4:Charts" width="799" height="289"&gt;&lt;/a&gt;&lt;br&gt;
Insert screenshot of your charts here.&lt;/p&gt;

&lt;p&gt;Screenshot: 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%2Fi6isiwrel59321jq2ogy.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%2Fi6isiwrel59321jq2ogy.png" alt="Figure 5: Dashboard" width="800" height="326"&gt;&lt;/a&gt;&lt;br&gt;
Insert screenshot of your completed Dashboard here.&lt;/p&gt;

&lt;h2&gt;
  
  
  Key Business Insights
&lt;/h2&gt;

&lt;p&gt;The analysis produced five major conclusions.&lt;/p&gt;

&lt;h3&gt;
  
  
  1. Large discounts do not guarantee engagement
&lt;/h3&gt;

&lt;p&gt;The discount-review correlation was approximately -0.14, showing a very weak negative relationship.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. Higher prices do not guarantee better ratings
&lt;/h3&gt;

&lt;p&gt;The price-rating correlation was approximately +0.11.&lt;/p&gt;

&lt;h3&gt;
  
  
  3. Review volume and rating measure different aspects of
&lt;/h3&gt;

&lt;p&gt;performance&lt;br&gt;
Review count reflects customer engagement, while rating provides an indication of customer satisfaction.&lt;/p&gt;

&lt;h3&gt;
  
  
  4. High-performing products combine engagement and satisfaction
&lt;/h3&gt;

&lt;p&gt;Products such as the Cake Decorating Tool Set and Vernier Caliper demonstrated strong performance through both review volume and rating.&lt;/p&gt;

&lt;h3&gt;
  
  
  5. Some heavily discounted products still perform poorly
&lt;/h3&gt;

&lt;p&gt;High discounts cannot compensate for poor product quality or customer dissatisfaction.&lt;/p&gt;

&lt;h2&gt;
  
  
  Recommendations for Jumia Sellers
&lt;/h2&gt;

&lt;p&gt;Based on the findings, sellers should consider the following strategies.&lt;/p&gt;

&lt;h3&gt;
  
  
  1. Do not rely entirely on discounts
&lt;/h3&gt;

&lt;p&gt;Discounting should be part of a broader marketing strategy rather than the only method used to generate engagement.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. Monitor customer ratings carefully
&lt;/h3&gt;

&lt;p&gt;A product receiving many reviews but maintaining a low rating should be investigated immediately.&lt;/p&gt;

&lt;h3&gt;
  
  
  3. Prioritize high-rated and highly reviewed products
&lt;/h3&gt;

&lt;p&gt;Products demonstrating both strong engagement and customer satisfaction should receive greater promotional attention.&lt;/p&gt;

&lt;h3&gt;
  
  
  4. Improve product quality
&lt;/h3&gt;

&lt;p&gt;Products with consistently poor ratings should be reviewed for quality and functionality issues.&lt;/p&gt;

&lt;h3&gt;
  
  
  5. Improve product descriptions
&lt;/h3&gt;

&lt;p&gt;Accurate product descriptions and specifications can help reduce differences between customer expectations and the actual product.&lt;/p&gt;

&lt;h3&gt;
  
  
  6. Use competitive pricing
&lt;/h3&gt;

&lt;p&gt;The analysis indicates that higher prices do not automatically result in higher ratings. Sellers should therefore balance profitability with customer-perceived value.&lt;/p&gt;

&lt;h3&gt;
  
  
  7. Use data for decision-making
&lt;/h3&gt;

&lt;p&gt;Instead of deciding on pricing and promotions based solely on intuition, sellers can use review counts, ratings, discounts, and prices together to identify products requiring action.&lt;/p&gt;

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

&lt;p&gt;This project demonstrated how Microsoft Excel can be used to transform raw e-commerce data into an interactive business intelligence dashboard.&lt;br&gt;
The analysis showed that product performance cannot be explained by discounts or prices alone.&lt;br&gt;
The weak relationship between discounts and reviews indicates that larger discounts do not automatically produce higher engagement. Similarly, the weak relationship between price and ratings indicates that expensive products are not necessarily better rated.&lt;br&gt;
The most useful approach is to evaluate multiple indicators together.&lt;br&gt;
Products with high review counts and strong ratings represent stronger performers, while products with high engagement but poor ratings may require quality or customer-experience improvements.&lt;br&gt;
Ultimately, the dashboard provides sellers with a practical way to identify opportunities, monitor product performance, and make more evidence-based pricing and marketing decisions.&lt;/p&gt;

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

&lt;p&gt;The key lesson from this project is simple:&lt;br&gt;
A large discount can attract attention, but customer satisfaction and product value are essential for sustainable product performance.&lt;/p&gt;

</description>
      <category>analysis</category>
      <category>data</category>
      <category>productivity</category>
    </item>
    <item>
      <title>Getting Started With Excel For Data Analytics: From Basic Data to Cleaning</title>
      <dc:creator>Victoria Ndei</dc:creator>
      <pubDate>Sat, 29 Aug 2026 20:47:00 +0000</pubDate>
      <link>https://dev.to/victoria_ndei_07224d0b8db/getting-started-with-excel-for-data-analytics-from-basic-data-to-cleaning-3ndp</link>
      <guid>https://dev.to/victoria_ndei_07224d0b8db/getting-started-with-excel-for-data-analytics-from-basic-data-to-cleaning-3ndp</guid>
      <description>&lt;h2&gt;
  
  
  Introduction
&lt;/h2&gt;

&lt;p&gt;This week at Luxdevhq, our data analyst group, we dove into Excel, learning how to navigate it and use its tools as real analysts. Join me as I explain what we covered this week. But before we go any further, we must first understand what Excel is.&lt;/p&gt;

&lt;h2&gt;
  
  
  What is Excel
&lt;/h2&gt;

&lt;p&gt;Excel is a spreadsheet software program designed by Microsoft that allows you to collect, organize, analyze, calculate, and visualize data efficiently. It allows users to create charts,and manage large datasets, making it one of the most widely used tools in data analysis and business reporting.&lt;/p&gt;

&lt;h2&gt;
  
  
  1 Excel Fundamentals
&lt;/h2&gt;

&lt;h3&gt;
  
  
  1.1 Workbooks, Worksheets and Cells
&lt;/h3&gt;

&lt;p&gt;Excel file is called a workbook, and a workbook is made of one or more worksheets(often called sheets), visibly seen as tabs at the bottom of the screen. Each worksheet is a grid of cells arranged into columns labelled with letters A, B, C... and rows labelled with numbers:1,2,3...&lt;br&gt;
Every cell has a unique address formed by its column and row, such as A2 or G12, and that address is what formulas use to refer to data elsewhere in the sheet.&lt;/p&gt;

&lt;h3&gt;
  
  
  1.2 Data Types
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Numbers: plain quantities use in calculations e.g: 2, 56, 134&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Texts: words or codes that Excel won't try to calculate e.g Nairobi&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Dates: stored internally as serial numbers;1/12/2000&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Formulas: expression begins with equals sign(=) that calculates result from other cells. e.g (=A2+C2) &lt;/p&gt;
&lt;h3&gt;
  
  
  The Ribbon and Navigation
&lt;/h3&gt;

&lt;p&gt;The ribbon is the toolbar across the top that contains all commands organized into tabs (Home, Insert, Page Layout, Formulas, Data, Review) being the most commonly used.&lt;br&gt;
Home tab covers formatting(fonts,colors,number formats),the formula tab gives access to the function library, and the Data tab is where most data-cleaning tools are found, including Removing Duplicates, Text to Columns, Data Validation, and Sort&amp;amp; Filter. &lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  1.4 Basic Formulas and Functions
&lt;/h3&gt;

&lt;p&gt;A &lt;strong&gt;formula&lt;/strong&gt; is an expression that calculates the value of a cell. Formulas begin with an equals (=) sign and can include:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Constants (numbers like 100)&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Cell references(like A1, B2)&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Operations like (+, -, *, /)&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Built-in functions (like SUM, AVERAGE)&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;FUNCTIONS&lt;/strong&gt;&lt;br&gt;
The following are some of the functions we went through this week:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;=SUM() - adds a range of numbers&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;=AVERAGE() -calculates the mean of a range&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;=COUNT()/=COUNT() -counts numeric cells, or non-blank cells respectively.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;=COUNTIFS() and =SUMIFS() -conditional counting and summing&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

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

&lt;p&gt;This section we will walk through a realistic sample dataset: fifteen order records from a small online store selling electronics and furniture. This dataset was built in away that it contains different problems every analyst runs into early in a project; inconsistent capitalization, stray whitespace, mixed data formats, a duplicate record, missing values, and a data entry error&lt;/p&gt;

&lt;h3&gt;
  
  
  2.1 The Raw Data
&lt;/h3&gt;

&lt;p&gt;Below is the "Raw Data" worksheet exactly as it may arrive from an export or a manual entry log. A few problems can be noted immediately, a few become obvious once formulas are applied to check for 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%2Fnvwu2vn5q2l9cx81258x.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fnvwu2vn5q2l9cx81258x.jpg" alt="Figure1:raw, uncleaned data" width="800" height="214"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  2.2 Standardizing Text with TRIM, PROPER, and LOWER
&lt;/h3&gt;

&lt;p&gt;These text functions mostly do most of the work when cleaning inconsistencies text entries:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;=TRIM() -removes leading, trailing, and repeated internal spaces (e.g, turns " Peter Otieno" into "Mary Achieng")&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;=PROPER() -converts text to Title Case, so "john mwangi" and "MARY ACHIENG" to "John Mwangi" and "Mary Achieng"&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;=LOWER() -converts texts to lowercase, useful for standardizing email addresses so "&lt;a href="mailto:JOHNMWANGI@GMAIL.COM"&gt;JOHNMWANGI@GMAIL.COM&lt;/a&gt; and &lt;a href="mailto:johnmwangi@gmail.com"&gt;johnmwangi@gmail.com&lt;/a&gt; are recognized as equal.
### 2.3 Handling Dates and Catching Format Errors
Data in Excel can sometimes look correct but actually can be stored incorrectly, which can cause problems when calculating and sorting data. e.g a date like 13/06/2023 might be understood differently depends on Excel's regional settings. The formula checks whether Excel can understand the entry as a real date. If it's a real date, it changes it format DD-MMM-YYYY such as 13-05-2023
### 2.4 Flagging Duplicates, Blanks, and Errors
This step is about finding problems in a data automatically instead of checking rows manually. 
Three checks are used:&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%2Fn6l69041vci6hn0589uj.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fn6l69041vci6hn0589uj.jpg" alt="Figure2:The cleaning formulas sheet" width="800" height="244"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Duplicate detection&lt;/strong&gt;: Finds records that appear more than once and marks them as "Duplicate"&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Blank Detection&lt;/strong&gt;: Finds cells where information is missing and marks them as &lt;strong&gt;"Yes"&lt;/strong&gt;.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Price validation&lt;/strong&gt;: Checks whether the price is negative. If it is, it shows "Negative Price"; otherwise, it shows &lt;strong&gt;OK&lt;/strong&gt;.&lt;br&gt;
Conditional formatting is used to highlight these problems in red problems making them easy to spot:&lt;/p&gt;
&lt;h3&gt;
  
  
  2.5 Producing the Final, Clean Dataset
&lt;/h3&gt;

&lt;p&gt;With every issue identified, the final step combines Excel's inbuilt data; Remove duplicates with the corrections suggested by the flag columns: the duplicate order (1007) was removed, the negative price on order was corrected to its intended positive value, missing quantities and regions were filled after checking the original records, and every data was rewritten in single consistent format. The result is a clean analysis ready table:&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%2Fm1xrlqvrm82plfkg7g22.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fm1xrlqvrm82plfkg7g22.jpg" alt="Figure3:The final cleaned dataset &lt;br&gt;
" width="799" height="280"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Key Takeaways
&lt;/h2&gt;

&lt;p&gt;Data cleaning is very important in data analysis. Excel functions like TRIM, PROPER, LOWER, COUNTSIFS help fix common data problems.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Use conditional formatting to highlight problems so that they are easy to see.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Always check dates. A date may look correct but could be stored as a text, which can cause errors.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Use formulas for analysis instead of manually typing.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>beginners</category>
      <category>analytics</category>
      <category>productivity</category>
    </item>
    <item>
      <title>My First GitHub Project: From a Local Folder to GitHub using Git and SSH</title>
      <dc:creator>Victoria Ndei</dc:creator>
      <pubDate>Sat, 22 Aug 2026 14:55:35 +0000</pubDate>
      <link>https://dev.to/victoria_ndei_07224d0b8db/my-first-github-project-from-a-local-folder-to-github-using-git-and-ssh-5cbc</link>
      <guid>https://dev.to/victoria_ndei_07224d0b8db/my-first-github-project-from-a-local-folder-to-github-using-git-and-ssh-5cbc</guid>
      <description>&lt;h2&gt;
  
  
  Introduction
&lt;/h2&gt;

&lt;p&gt;This week I learnt how to create my first  GitHub project from a local folder on my desktop using tools such as Git and an SSH connection. Git is used to track changes in the source code and files over time, while SSH is a key used for authenticating your desktop with your GitHub account. There are several steps that we need to follow before connecting our project to our GitHub account.&lt;/p&gt;

&lt;h2&gt;
  
  
  1 Creating a Folder
&lt;/h2&gt;

&lt;p&gt;There are two ways to create a folder; the first way is to go to your file explorer and select Desktop to create the folder directly there. The second step is to use Git Bash. When using Git Bash, first, you run the command &lt;strong&gt;pwd&lt;/strong&gt; to see which folder you're currently in; e.g when using Windows, you may see &lt;em&gt;/c/Users/John _&lt;/em&gt;.To change your directory to Desktop, you run the command&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;cd &lt;/span&gt;Desktop
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then run &lt;strong&gt;ls&lt;/strong&gt; to see the files located on your desktop. Now we create our folder. To create a folder, we run&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;mkdir &lt;/span&gt;Kenya_Health_Records
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then we run *&lt;em&gt;cd Kenya_Health_Records *&lt;/em&gt; to change the directory to our folder.&lt;/p&gt;

&lt;h2&gt;
  
  
  2 Adding Files to The Folder
&lt;/h2&gt;

&lt;p&gt;This is our second step after creating our folder. We are going to add the following folders to our project folder:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;data &lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;notebooks&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;scripts&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;To add the folders, we will run the following commands:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;mkdir &lt;/span&gt;data

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;mkdir &lt;/span&gt;notebooks
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;mkdir &lt;/span&gt;scripts
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We will also add our README.md using the command:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;&lt;br&gt;
touch README.md&lt;br&gt;
&lt;/code&gt;`shell&lt;/p&gt;
&lt;h3&gt;
  
  
  Adding information to our README.md
&lt;/h3&gt;

&lt;p&gt;To add information to our README, we use the echo command, two double quotes, and the&amp;gt; sign.&lt;br&gt;
Example:&lt;br&gt;
&lt;code&gt;&lt;/code&gt;&lt;code&gt;&lt;br&gt;
echo " ## My First GitHub Project" &amp;gt;README.md&lt;br&gt;
&lt;/code&gt;&lt;code&gt;&lt;/code&gt;shell&lt;br&gt;
If you want to check that the information is added to the README.md file, we use&lt;/p&gt;

&lt;p&gt;&lt;code&gt;&lt;/code&gt;&lt;code&gt;&lt;br&gt;
cat README.md&lt;br&gt;
&lt;/code&gt;&lt;code&gt;&lt;/code&gt;shell&lt;/p&gt;
&lt;h2&gt;
  
  
  3 Initializing the Project with Git
&lt;/h2&gt;

&lt;p&gt;To initialize the project, we use the command &lt;strong&gt;git init&lt;/strong&gt;&lt;br&gt;
git init initializes the directory as a Git repository.&lt;br&gt;
When I ran git init, it created a hidden .git directory inside my project to track changes.&lt;br&gt;
Afterwards, I checked the status of my files using &lt;strong&gt;git status&lt;/strong&gt;.&lt;/p&gt;
&lt;h2&gt;
  
  
  4 Stage the Whole Project
&lt;/h2&gt;

&lt;p&gt;To stage the remaining changes to the whole project, I ran:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;&lt;/code&gt;&lt;code&gt;&lt;br&gt;
git add.&lt;br&gt;
&lt;/code&gt;&lt;code&gt;&lt;/code&gt;shell&lt;br&gt;
The period (.) tells Git to add all files to the current directory. I ran *&lt;em&gt;git status *&lt;/em&gt; to check if the files have been added.&lt;/p&gt;
&lt;h2&gt;
  
  
  5 Creating the First Commit
&lt;/h2&gt;

&lt;p&gt;Run: git commit-m" Add Kenya Health Records`&lt;br&gt;
git commit is a snapshot of the changes made to the project.&lt;/p&gt;
&lt;h2&gt;
  
  
  6 Check Current Branch
&lt;/h2&gt;

&lt;p&gt;Run:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;git branch
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;&lt;br&gt;
shell&lt;br&gt;
You may see *main, which is the current branch we are in.&lt;br&gt;
For our beginner project, we will use main as our primary branch. If your branch says master, rename it using git branch -M main`&lt;/p&gt;

&lt;h2&gt;
  
  
  7 Creating GitHub repository
&lt;/h2&gt;

&lt;p&gt;Sign in to your GitHub account and then click New repository.&lt;br&gt;
 Name your repository as Kenya-Health-Records. &lt;br&gt;
You can choose whether to set it as private or public.&lt;br&gt;
Our project already has a README.md, so do not select to add it to the repository.&lt;/p&gt;

&lt;h2&gt;
  
  
  8 Selecting SSH
&lt;/h2&gt;

&lt;p&gt;After creating the repository, GitHub displays connection options like HTTPS and SSH. &lt;br&gt;
Select SSH because it was already configured earlier.&lt;br&gt;
The address will look similar to:&lt;a href="mailto:git@github.com"&gt;git@github.com&lt;/a&gt;:USERNAME/kenyan-hospital-health-records.git &lt;/p&gt;

&lt;h2&gt;
  
  
  9 Connecting the Local Repository to GitHub
&lt;/h2&gt;

&lt;p&gt;Open your git bash and run;&lt;/p&gt;

&lt;p&gt;&lt;code&gt;&lt;/code&gt;&lt;code&gt;&lt;br&gt;
git remote add origin git@github.com:USERNAME/kenyan-hospital-health-records.git &lt;br&gt;
&lt;/code&gt;&lt;code&gt;&lt;/code&gt;shell&lt;br&gt;
I replaced the username with my GitHub username.&lt;br&gt;
I verified the remote connection using:&lt;br&gt;
&lt;code&gt;git remote-v&lt;/code&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  10 Pushing the Project to GitHub
&lt;/h2&gt;

&lt;p&gt;Run:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;&lt;/code&gt;&lt;code&gt;&lt;br&gt;
git push -u origin main&lt;br&gt;
&lt;/code&gt;&lt;code&gt;&lt;/code&gt;&lt;br&gt;
git push sends the changes from my computer to my remote repository.&lt;/p&gt;

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

&lt;p&gt;After the push was completed, I went to GitHub and refreshed the page to see if my project files had been added to the repository.&lt;/p&gt;

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

&lt;p&gt;I gained practical experience in how to upload my local folder using Git and SSH on GitHub. The experience was truly great, and now I'm more familiar with the tools, making it easier to work on more projects on my own from henceforth.&lt;/p&gt;

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