DEV Community

Samuel Mwai
Samuel Mwai

Posted on

# SQL Joins and Window Functions: A Practical Guide

SQL Joins and Window Functions: A Practical Guide

Introduction

SQL is one of the most important tools for working with data. Whether you are analyzing sales, customers, employees, financial transactions, or business performance, SQL allows you to retrieve, combine, transform, and analyze data stored in relational databases.

Two particularly important SQL concepts are joins and window functions.

Joins allow you to combine information from multiple tables, while window functions allow you to perform calculations across related rows without collapsing the individual rows.

Understanding both concepts is essential for anyone working in data analysis, data science, business intelligence, or database development.


Part 1: SQL Joins

What Is a SQL Join?

A SQL join is used to combine rows from two or more tables based on a related column.

For example, imagine we have two tables:

Customers

customer_id customer_name country
1 John Kenya
2 Mary Uganda
3 Peter Tanzania

Orders

order_id customer_id amount
101 1 50000
102 2 30000
103 1 20000

The common column is customer_id.

We can use it to connect the two tables:

SELECT
    customers.customer_name,
    orders.order_id,
    orders.amount
FROM customers
JOIN orders
    ON customers.customer_id = orders.customer_id;
Enter fullscreen mode Exit fullscreen mode

The result would be:

customer_name order_id amount
John 101 50000
Mary 102 30000
John 103 20000

The join allows us to combine customer information with order information.


1. INNER JOIN

An INNER JOIN returns only rows that have matching values in both tables.

SELECT
    c.customer_name,
    o.order_id,
    o.amount
FROM customers c
INNER JOIN orders o
    ON c.customer_id = o.customer_id;
Enter fullscreen mode Exit fullscreen mode

If a customer has never placed an order, that customer will not appear in the result.

For example:

Customers          Orders

1 John             1
2 Mary             2
3 Peter
Enter fullscreen mode Exit fullscreen mode

An inner join returns John and Mary because they have matching orders.

When to use INNER JOIN

Use an inner join when you only want records that exist in both tables.

Examples include:

  • Customers who have placed orders
  • Employees assigned to departments
  • Products that have sales
  • Students enrolled in courses

2. LEFT JOIN

A LEFT JOIN returns every row from the left table and matching rows from the right table.

SELECT
    c.customer_name,
    o.order_id,
    o.amount
FROM customers c
LEFT JOIN orders o
    ON c.customer_id = o.customer_id;
Enter fullscreen mode Exit fullscreen mode

The result might be:

customer_name order_id amount
John 101 50000
Mary 102 30000
John 103 20000
Peter NULL NULL

Peter appears even though he has no order.

This is one of the most useful joins in data analysis because it allows you to find records that don't have matching information.

For example:

SELECT
    c.customer_name
FROM customers c
LEFT JOIN orders o
    ON c.customer_id = o.customer_id
WHERE o.order_id IS NULL;
Enter fullscreen mode Exit fullscreen mode

This finds customers who have never placed an order.


3. RIGHT JOIN

A RIGHT JOIN returns every row from the right table and matching rows from the left table.

SELECT
    c.customer_name,
    o.order_id,
    o.amount
FROM customers c
RIGHT JOIN orders o
    ON c.customer_id = o.customer_id;
Enter fullscreen mode Exit fullscreen mode

RIGHT JOIN is less commonly used because the same result can usually be achieved by switching the table order and using a LEFT JOIN.

For example:

FROM orders o
LEFT JOIN customers c
Enter fullscreen mode Exit fullscreen mode

is often easier to read.


4. FULL OUTER JOIN

A FULL OUTER JOIN returns all rows from both tables.

If a row has no match, SQL fills the missing values with NULL.

SELECT
    c.customer_name,
    o.order_id,
    o.amount
FROM customers c
FULL OUTER JOIN orders o
    ON c.customer_id = o.customer_id;
Enter fullscreen mode Exit fullscreen mode

This is useful when you want to identify records that exist in one table but not the other.

For example, you might use it to compare two datasets and find:

  • Customers missing from another system
  • Products missing from an inventory database
  • Transactions that don't match
  • Data quality problems

5. CROSS JOIN

A CROSS JOIN produces every possible combination of rows from two tables.

Suppose:

Products

Laptop
Phone
Tablet
Enter fullscreen mode Exit fullscreen mode

and:

Countries

Kenya
Uganda
Tanzania
Enter fullscreen mode Exit fullscreen mode

A cross join produces:

Laptop  Kenya
Laptop  Uganda
Laptop  Tanzania
Phone   Kenya
Phone   Uganda
Phone   Tanzania
Tablet  Kenya
Tablet  Uganda
Tablet  Tanzania
Enter fullscreen mode Exit fullscreen mode

SQL:

SELECT
    p.product_name,
    c.country
FROM products p
CROSS JOIN countries c;
Enter fullscreen mode Exit fullscreen mode

If one table has 3 rows and another has 3 rows, the result contains:

3 × 3 = 9 rows
Enter fullscreen mode Exit fullscreen mode

Because cross joins can produce very large results, they should be used carefully.


6. SELF JOIN

A SELF JOIN joins a table to itself.

This is useful when records within the same table are related.

For example, an employee table might contain:

employee_id employee_name manager_id
1 John NULL
2 Mary 1
3 Peter 1

Here, manager_id refers back to employee_id in the same table.

We can use a self join to display each employee and their manager:

SELECT
    e.employee_name AS employee,
    m.employee_name AS manager
FROM employees e
LEFT JOIN employees m
    ON e.manager_id = m.employee_id;
Enter fullscreen mode Exit fullscreen mode

Result:

employee manager
John NULL
Mary John
Peter John

Part 2: SQL Window Functions

What Is a Window Function?

A window function performs a calculation across a set of related rows while keeping the individual rows in the result.

This is the key difference between window functions and regular aggregate functions.

For example, consider:

employee department salary
John IT 60000
Mary IT 70000
Peter Sales 50000
Jane Sales 80000

A regular aggregation might calculate:

SELECT
    department,
    AVG(salary)
FROM employees
GROUP BY department;
Enter fullscreen mode Exit fullscreen mode

Result:

department average_salary
IT 65000
Sales 65000

The individual employees disappear from the result.

A window function allows us to calculate the average while keeping every employee:

SELECT
    employee,
    department,
    salary,
    AVG(salary) OVER (
        PARTITION BY department
    ) AS department_average
FROM employees;
Enter fullscreen mode Exit fullscreen mode

Result:

employee department salary department_average
John IT 60000 65000
Mary IT 70000 65000
Peter Sales 50000 65000
Jane Sales 80000 65000

This is why window functions are extremely powerful for analytics.


7. The OVER() Clause

Window functions use the OVER() clause.

For example:

AVG(salary) OVER ()
Enter fullscreen mode Exit fullscreen mode

The OVER() clause tells SQL that we want the calculation to operate as a window over the result set.

There are two particularly important components:

PARTITION BY
ORDER BY
Enter fullscreen mode Exit fullscreen mode

8. PARTITION BY

PARTITION BY divides the data into groups for the window calculation.

For example:

AVG(salary) OVER (
    PARTITION BY department
)
Enter fullscreen mode Exit fullscreen mode

This calculates the average salary separately for each department.

It is similar to GROUP BY, but it does not collapse the rows.

Think of it as:

GROUP BY → reduces rows

PARTITION BY → keeps rows
Enter fullscreen mode Exit fullscreen mode

9. ORDER BY in Window Functions

ORDER BY determines the order in which the window function processes rows.

For example:

SUM(sales) OVER (
    ORDER BY sale_date
)
Enter fullscreen mode Exit fullscreen mode

This can be used to calculate a running total.

Suppose we have:

sale_date sales
Jan 1 100
Jan 2 200
Jan 3 150

Query:

SELECT
    sale_date,
    sales,
    SUM(sales) OVER (
        ORDER BY sale_date
    ) AS running_sales
FROM sales;
Enter fullscreen mode Exit fullscreen mode

Result:

sale_date sales running_sales
Jan 1 100 100
Jan 2 200 300
Jan 3 150 450

This is called a running total.


10. ROW_NUMBER()

ROW_NUMBER() assigns a unique sequential number to each row.

SELECT
    employee,
    salary,
    ROW_NUMBER() OVER (
        ORDER BY salary DESC
    ) AS row_number
FROM employees;
Enter fullscreen mode Exit fullscreen mode

Result:

employee salary row_number
Jane 80000 1
Mary 70000 2
John 60000 3
Peter 50000 4

You can also partition the results:

ROW_NUMBER() OVER (
    PARTITION BY department
    ORDER BY salary DESC
)
Enter fullscreen mode Exit fullscreen mode

This ranks employees separately within each department.


11. RANK()

RANK() assigns rankings but gives tied values the same rank.

For example:

employee salary
Jane 80000
Mary 70000
John 70000
Peter 50000

Query:

SELECT
    employee,
    salary,
    RANK() OVER (
        ORDER BY salary DESC
    ) AS salary_rank
FROM employees;
Enter fullscreen mode Exit fullscreen mode

Result:

employee salary salary_rank
Jane 80000 1
Mary 70000 2
John 70000 2
Peter 50000 4

Notice that there is no rank 3 because Mary and John share rank 2.


12. DENSE_RANK()

DENSE_RANK() is similar to RANK(), but it does not skip ranking numbers.

Using the same data:

SELECT
    employee,
    salary,
    DENSE_RANK() OVER (
        ORDER BY salary DESC
    ) AS salary_rank
FROM employees;
Enter fullscreen mode Exit fullscreen mode

Result:

employee salary salary_rank
Jane 80000 1
Mary 70000 2
John 70000 2
Peter 50000 3

The difference is:

ROW_NUMBER()
1, 2, 3, 4

RANK()
1, 2, 2, 4

DENSE_RANK()
1, 2, 2, 3
Enter fullscreen mode Exit fullscreen mode

13. LAG()

LAG() allows you to access a value from a previous row.

This is extremely useful when analyzing changes over time.

For example:

SELECT
    sale_date,
    sales,
    LAG(sales) OVER (
        ORDER BY sale_date
    ) AS previous_sales
FROM sales;
Enter fullscreen mode Exit fullscreen mode

Result:

sale_date sales previous_sales
Jan 1 100 NULL
Jan 2 200 100
Jan 3 150 200

We can then calculate the change:

SELECT
    sale_date,
    sales,
    sales - LAG(sales) OVER (
        ORDER BY sale_date
    ) AS sales_change
FROM sales;
Enter fullscreen mode Exit fullscreen mode

Result:

sale_date sales sales_change
Jan 1 100 NULL
Jan 2 200 100
Jan 3 150 -50

This is useful for:

  • Month-over-month analysis
  • Year-over-year analysis
  • Stock analysis
  • Customer activity
  • Revenue changes

14. LEAD()

LEAD() does the opposite of LAG().

It allows you to access a value from a future row.

SELECT
    sale_date,
    sales,
    LEAD(sales) OVER (
        ORDER BY sale_date
    ) AS next_sales
FROM sales;
Enter fullscreen mode Exit fullscreen mode

Result:

sale_date sales next_sales
Jan 1 100 200
Jan 2 200 150
Jan 3 150 NULL

15. FIRST_VALUE() and LAST_VALUE()

Window functions can also retrieve the first or last value within a window.

For example:

SELECT
    employee,
    department,
    salary,
    FIRST_VALUE(salary) OVER (
        PARTITION BY department
        ORDER BY salary DESC
    ) AS highest_salary
FROM employees;
Enter fullscreen mode Exit fullscreen mode

This allows every employee to see the highest salary within their department.


16. Window Functions vs GROUP BY

One of the most important concepts to understand is the difference between GROUP BY and window functions.

GROUP BY

SELECT
    department,
    AVG(salary) AS average_salary
FROM employees
GROUP BY department;
Enter fullscreen mode Exit fullscreen mode

Produces one row per department.

Window Function

SELECT
    employee,
    department,
    salary,
    AVG(salary) OVER (
        PARTITION BY department
    ) AS average_salary
FROM employees;
Enter fullscreen mode Exit fullscreen mode

Keeps every employee while adding the department average.

In simple terms:

GROUP BY summarizes rows.

Window functions analyze rows without removing them.


17. Combining Joins and Window Functions

Joins and window functions are often used together in real-world data analysis.

Suppose you have:

Customers
Orders
Enter fullscreen mode Exit fullscreen mode

You could first join the tables:

SELECT
    c.customer_name,
    o.order_date,
    o.amount
FROM customers c
JOIN orders o
    ON c.customer_id = o.customer_id;
Enter fullscreen mode Exit fullscreen mode

Then use a window function to rank each customer's orders:

SELECT
    c.customer_name,
    o.order_date,
    o.amount,
    ROW_NUMBER() OVER (
        PARTITION BY c.customer_id
        ORDER BY o.amount DESC
    ) AS order_rank
FROM customers c
JOIN orders o
    ON c.customer_id = o.customer_id;
Enter fullscreen mode Exit fullscreen mode

Now you can identify the largest order for each customer.


18. Finding the Top Record per Group

One of the most common real-world uses of window functions is finding the top record within each group.

For example, suppose you want the highest-paid employee in every department.

First:

SELECT
    employee,
    department,
    salary,
    ROW_NUMBER() OVER (
        PARTITION BY department
        ORDER BY salary DESC
    ) AS rn
FROM employees;
Enter fullscreen mode Exit fullscreen mode

Then use a subquery:

SELECT *
FROM (
    SELECT
        employee,
        department,
        salary,
        ROW_NUMBER() OVER (
            PARTITION BY department
            ORDER BY salary DESC
        ) AS rn
    FROM employees
) ranked
WHERE rn = 1;
Enter fullscreen mode Exit fullscreen mode

This returns the highest-paid employee from each department.


19. Real-World Applications

SQL joins and window functions are widely used in data analytics.

Sales Analysis

You can use joins to combine:

Customers
Products
Orders
Sales
Enter fullscreen mode Exit fullscreen mode

Then use window functions to calculate:

  • Customer rankings
  • Running revenue
  • Previous month's sales
  • Top products
  • Sales growth

Financial Analysis

Window functions can be used for:

  • Running balances
  • Transaction rankings
  • Month-over-month changes
  • Portfolio performance
  • Cumulative revenue

Human Resources

You can calculate:

  • Employee salary rankings
  • Department averages
  • Highest-paid employees
  • Employee salary differences
  • Hiring trends

Customer Analytics

You can analyze:

  • First purchase
  • Most recent purchase
  • Customer order rankings
  • Previous purchases
  • Customer spending trends

20. Best Practices

When working with joins and window functions, several practices can make your SQL easier to understand and more reliable.

Use table aliases

Instead of:

customers.customer_name
Enter fullscreen mode Exit fullscreen mode

use:

c.customer_name
Enter fullscreen mode Exit fullscreen mode

after defining:

FROM customers c
Enter fullscreen mode Exit fullscreen mode

Always specify the join condition

For example:

ON c.customer_id = o.customer_id
Enter fullscreen mode Exit fullscreen mode

Avoid accidentally creating a Cartesian product unless you intentionally need a CROSS JOIN.

Understand your keys

Before joining tables, determine whether the join key is unique.

Joining a one-to-many table incorrectly can create duplicate rows and inflate totals.

Use meaningful window ordering

For example:

ORDER BY sale_date
Enter fullscreen mode Exit fullscreen mode

is meaningful for a running sales calculation.

Use PARTITION BY when analysis needs groups

For example:

PARTITION BY customer_id
Enter fullscreen mode Exit fullscreen mode

allows calculations to restart for every customer.


Conclusion

SQL joins and window functions are essential skills for data analysts and data scientists.

Joins allow you to bring information from different tables together, while window functions allow you to analyze related rows while preserving the original rows.

The most important joins to understand are:

  • INNER JOIN
  • LEFT JOIN
  • RIGHT JOIN
  • FULL OUTER JOIN
  • CROSS JOIN
  • SELF JOIN

The most useful window functions include:

  • ROW_NUMBER()
  • RANK()
  • DENSE_RANK()
  • LAG()
  • LEAD()
  • SUM() OVER()
  • AVG() OVER()
  • FIRST_VALUE()
  • LAST_VALUE()

A simple way to remember the concepts is:

Joins combine tables. Window functions analyze rows.

Once you understand these two concepts, you can solve much more advanced SQL problems, from identifying top-performing customers to calculating running totals and comparing current performance with previous periods.

Top comments (0)