<?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: Nginda Nganga</title>
    <description>The latest articles on DEV Community by Nginda Nganga (@nginda_nganga_c06fc51a205).</description>
    <link>https://dev.to/nginda_nganga_c06fc51a205</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F3801168%2Fe0dc0a7b-397d-4f30-999b-782139dbaa60.jpg</url>
      <title>DEV Community: Nginda Nganga</title>
      <link>https://dev.to/nginda_nganga_c06fc51a205</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/nginda_nganga_c06fc51a205"/>
    <language>en</language>
    <item>
      <title>SQL Joins and Window Functions- What I Learned Catching Up After Missing Class..</title>
      <dc:creator>Nginda Nganga</dc:creator>
      <pubDate>Mon, 02 Mar 2026 11:03:17 +0000</pubDate>
      <link>https://dev.to/nginda_nganga_c06fc51a205/sql-joins-and-window-functions-what-i-learned-catching-up-after-missing-class-49d</link>
      <guid>https://dev.to/nginda_nganga_c06fc51a205/sql-joins-and-window-functions-what-i-learned-catching-up-after-missing-class-49d</guid>
      <description>&lt;p&gt;&lt;strong&gt;Introduction&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;SQL Joins and Window Functions are essential tools for any data professional. Joins help combine data from multiple tables, while Window Functions allow calculations across rows without collapsing them. This guide breaks down both concepts with simple examples using African names and cities.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Part 1: SQL Joins&lt;/strong&gt;&lt;br&gt;
Sample Tables&lt;/p&gt;

&lt;p&gt;First, let's create our sample data:&lt;br&gt;
sql&lt;/p&gt;

&lt;p&gt;-- Departments table&lt;br&gt;
CREATE TABLE departments (&lt;br&gt;
    dept_id INT PRIMARY KEY,&lt;br&gt;
    dept_name VARCHAR(50),&lt;br&gt;
    location VARCHAR(50)&lt;br&gt;
);&lt;/p&gt;

&lt;p&gt;INSERT INTO departments VALUES&lt;br&gt;
(1, 'Engineering', 'Nairobi, Kenya'),&lt;br&gt;
(2, 'Marketing', 'Cape Town, South Africa'),&lt;br&gt;
(3, 'Sales', 'Lagos, Nigeria'),&lt;br&gt;
(4, 'Legal', 'Nairobi, Kenya'),&lt;br&gt;
(5, 'HR', 'Kampala, Uganda');&lt;/p&gt;

&lt;p&gt;-- Employees table&lt;br&gt;
CREATE TABLE employees (&lt;br&gt;
    emp_id INT PRIMARY KEY,&lt;br&gt;
    emp_name VARCHAR(50),&lt;br&gt;
    dept_id INT,&lt;br&gt;
    salary DECIMAL(10,2)&lt;br&gt;
);&lt;/p&gt;

&lt;p&gt;INSERT INTO employees VALUES&lt;br&gt;
(1, 'Nginda Nganga', 1, 85000),&lt;br&gt;
(2, 'Kwame Mensah', 1, 75000),&lt;br&gt;
(3, 'Zanele Khumalo', 2, 65000),&lt;br&gt;
(4, 'Akinyi Odera', NULL, 55000),&lt;br&gt;
(5, 'Kacungira Shanice', NULL, 6600);&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;1. INNER JOIN&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Returns only matching records from both tables.&lt;br&gt;
sql&lt;/p&gt;

&lt;p&gt;SELECT e.emp_name, d.dept_name, d.location&lt;br&gt;
FROM employees e&lt;br&gt;
INNER JOIN departments d ON e.dept_id = d.dept_id;&lt;/p&gt;

&lt;p&gt;Result:&lt;br&gt;
emp_name    dept_name   location&lt;br&gt;
Nginda Nganga   Engineering Nairobi, Kenya&lt;br&gt;
Kwame Mensah    Engineering Nairobi, Kenya&lt;br&gt;
Zanele Khumalo  Marketing   Cape Town, South Africa&lt;/p&gt;

&lt;p&gt;Only employees with departments appear (Akinyi and Kacungira are excluded because they have no department).&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;2. LEFT JOIN&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Returns all records from the left table (employees).&lt;br&gt;
sql&lt;/p&gt;

&lt;p&gt;SELECT e.emp_name, d.dept_name, d.location&lt;br&gt;
FROM employees e&lt;br&gt;
LEFT JOIN departments d ON e.dept_id = d.dept_id;&lt;/p&gt;

&lt;p&gt;Result:&lt;br&gt;
emp_name    dept_name   location&lt;br&gt;
Nginda Nganga   Engineering Nairobi, Kenya&lt;br&gt;
Kwame Mensah    Engineering Nairobi, Kenya&lt;br&gt;
Zanele Khumalo  Marketing   Cape Town, South Africa&lt;br&gt;
Akinyi Odera    NULL    NULL&lt;br&gt;
Kacungira Shanice   NULL    NULL&lt;/p&gt;

&lt;p&gt;All employees appear. Akinyi and Kacungira show NULL for department since they're unassigned.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;RIGHT JOIN&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Returns all records from the right table (departments).&lt;br&gt;
sql&lt;/p&gt;

&lt;p&gt;SELECT e.emp_name, d.dept_name, d.location&lt;br&gt;
FROM employees e&lt;br&gt;
RIGHT JOIN departments d ON e.dept_id = d.dept_id;&lt;/p&gt;

&lt;p&gt;Result:&lt;br&gt;
emp_name    dept_name   location&lt;br&gt;
Nginda Nganga   Engineering Nairobi, Kenya&lt;br&gt;
Kwame Mensah    Engineering Nairobi, Kenya&lt;br&gt;
Zanele Khumalo  Marketing   Cape Town, South Africa&lt;br&gt;
NULL    Sales   Lagos, Nigeria&lt;br&gt;
NULL    Legal   Nairobi, Kenya&lt;br&gt;
NULL    HR  Kampala, Uganda&lt;/p&gt;

&lt;p&gt;All departments appear. Sales, Legal, and HR have no employees assigned.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;SELF JOIN&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Joining a table with itself.&lt;br&gt;
sql&lt;/p&gt;

&lt;p&gt;-- Find employees who share the same department&lt;br&gt;
SELECT &lt;br&gt;
    e1.emp_name AS employee1,&lt;br&gt;
    e2.emp_name AS employee2,&lt;br&gt;
    d.dept_name&lt;br&gt;
FROM employees e1&lt;br&gt;
INNER JOIN employees e2 ON e1.dept_id = e2.dept_id AND e1.emp_id &amp;lt; e2.emp_id&lt;br&gt;
INNER JOIN departments d ON e1.dept_id = d.dept_id;&lt;/p&gt;

&lt;p&gt;Result:&lt;br&gt;
employee1   employee2   dept_name&lt;br&gt;
Nginda Nganga   Kwame Mensah    Engineering&lt;/p&gt;

&lt;p&gt;Shows colleagues working in the same department.&lt;br&gt;
Part 2: Window Functions&lt;/p&gt;

&lt;p&gt;Window functions perform calculations across rows while keeping individual row details.&lt;br&gt;
Sample Sales Data&lt;br&gt;
sql&lt;/p&gt;

&lt;p&gt;-- Sales table&lt;br&gt;
CREATE TABLE sales (&lt;br&gt;
    sale_id INT PRIMARY KEY,&lt;br&gt;
    product VARCHAR(50),&lt;br&gt;
    category VARCHAR(50),&lt;br&gt;
    amount DECIMAL(10,2),&lt;br&gt;
    sale_date DATE&lt;br&gt;
);&lt;/p&gt;

&lt;p&gt;INSERT INTO sales VALUES&lt;br&gt;
(1, 'Maize Flour', 'Food', 12500, '2024-01-15'),&lt;br&gt;
(2, 'Coffee Beans', 'Beverages', 45000, '2024-01-15'),&lt;br&gt;
(3, 'Shea Butter', 'Beauty', 8900, '2024-01-18'),&lt;br&gt;
(4, 'Maize Flour', 'Food', 15200, '2024-01-20'),&lt;br&gt;
(5, 'Green Tea', 'Beverages', 85000, '2024-01-10');&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;ROW_NUMBER() - Ranking Items
sql&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;SELECT &lt;br&gt;
    product,&lt;br&gt;
    category,&lt;br&gt;
    amount,&lt;br&gt;
    ROW_NUMBER() OVER (PARTITION BY category ORDER BY amount DESC) AS rank&lt;br&gt;
FROM sales;&lt;/p&gt;

&lt;p&gt;Result:&lt;br&gt;
product category    amount  rank&lt;br&gt;
Green Tea   Beverages   85000   1&lt;br&gt;
Coffee Beans    Beverages   45000   2&lt;br&gt;
Shea Butter Beauty  8900    1&lt;br&gt;
Maize Flour Food    15200   1&lt;br&gt;
Maize Flour Food    12500   2&lt;/p&gt;

&lt;p&gt;Ranks products within each category by amount.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Running Total
sql&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;SELECT &lt;br&gt;
    sale_date,&lt;br&gt;
    product,&lt;br&gt;
    amount,&lt;br&gt;
    SUM(amount) OVER (ORDER BY sale_date) AS running_total&lt;br&gt;
FROM sales&lt;br&gt;
ORDER BY sale_date;&lt;/p&gt;

&lt;p&gt;Result:&lt;br&gt;
sale_date   product amount  running_total&lt;br&gt;
2024-01-10  Green Tea   85000   85000&lt;br&gt;
2024-01-15  Maize Flour 12500   97500&lt;br&gt;
2024-01-15  Coffee Beans    45000   142500&lt;br&gt;
2024-01-18  Shea Butter 8900    151400&lt;br&gt;
2024-01-20  Maize Flour 15200   166600&lt;/p&gt;

&lt;p&gt;Shows cumulative sales over time.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;LAG() - Compare with Previous Row
sql&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;SELECT &lt;br&gt;
    sale_date,&lt;br&gt;
    product,&lt;br&gt;
    amount,&lt;br&gt;
    LAG(amount, 1) OVER (ORDER BY sale_date) AS previous_amount,&lt;br&gt;
    amount - LAG(amount, 1, 0) OVER (ORDER BY sale_date) AS difference&lt;br&gt;
FROM sales;&lt;/p&gt;

&lt;p&gt;Result:&lt;br&gt;
sale_date   product amount  previous_amount difference&lt;br&gt;
2024-01-10  Green Tea   85000   NULL    85000&lt;br&gt;
2024-01-15  Maize Flour 12500   85000   -72500&lt;br&gt;
2024-01-15  Coffee Beans    45000   12500   32500&lt;br&gt;
2024-01-18  Shea Butter 8900    45000   -36100&lt;br&gt;
2024-01-20  Maize Flour 15200   8900    6300&lt;/p&gt;

&lt;p&gt;Compares each sale to the previous one.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Average by Department
sql&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;SELECT &lt;br&gt;
    emp_name,&lt;br&gt;
    dept_id,&lt;br&gt;
    salary,&lt;br&gt;
    AVG(salary) OVER (PARTITION BY dept_id) AS dept_avg_salary&lt;br&gt;
FROM employees&lt;br&gt;
WHERE dept_id IS NOT NULL;&lt;/p&gt;

&lt;p&gt;Result:&lt;br&gt;
emp_name    dept_id salary  dept_avg_salary&lt;br&gt;
Nginda Nganga   1   85000   80000&lt;br&gt;
Kwame Mensah    1   75000   80000&lt;br&gt;
Zanele Khumalo  2   65000   65000&lt;/p&gt;

&lt;p&gt;Shows each employee compared to their department average.&lt;/p&gt;

&lt;p&gt;Quick Reference&lt;/p&gt;

&lt;p&gt;JOIN Types&lt;/p&gt;

&lt;p&gt;Join Type - What It Returns&lt;br&gt;
INNER JOIN - Only matching records from both tables&lt;br&gt;
LEFT JOIN - All records from left table + matches from right&lt;br&gt;
RIGHT JOIN - All records from right table + matches from left&lt;br&gt;
FULL JOIN - All records from both tables&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Common Window Functions&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Function - What It Does&lt;br&gt;
ROW_NUMBER() - Assigns unique rank (1,2,3,4)&lt;br&gt;
RANK() - Same rank for ties, skips numbers (1,2,2,4)&lt;br&gt;
DENSE_RANK()- Same rank for ties, no skipping (1,2,2,3)&lt;br&gt;
LAG() - Access previous row's data&lt;br&gt;
LEAD() - Access next row's data&lt;br&gt;
SUM() - OVER() Running totals&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Summary&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Joins combine data from multiple tables based on relationships&lt;/p&gt;

&lt;p&gt;Window functions perform calculations across rows without grouping&lt;/p&gt;

&lt;p&gt;Use INNER JOIN for matched records, LEFT/RIGHT JOIN to preserve all records&lt;/p&gt;

&lt;p&gt;Use ROW_NUMBER() for ranking, LAG() for comparisons, SUM() OVER() for running totals&lt;/p&gt;

&lt;p&gt;Both tools are essential for writing efficient, powerful SQL queries in real-world applications.&lt;/p&gt;

&lt;p&gt;Nginda Nganga- a data science student, balancing life and hustle&lt;/p&gt;

</description>
      <category>sql</category>
      <category>database</category>
      <category>beginners</category>
    </item>
  </channel>
</rss>
