Common Table Expressions (CTEs) for Cleaner Queries

By Anurag Singh

Updated on Nov 13, 2024

Common Table Expressions (CTEs) for Cleaner Queries

In this tutorial, we'll learn Common Table Expressions (CTEs) for cleaner queries.

Introduction to CTEs

Common Table Expressions (CTEs) are a powerful SQL feature that allows you to break down complex queries into more readable, modular parts. Introduced in SQL:1999, CTEs are supported by most modern relational database systems like PostgreSQL, MySQL (since version 8.0), SQL Server, Oracle, and SQLite.

A CTE can be thought of as a temporary result set, or a "virtual table," that you can reference within a query. By using CTEs, you can structure your queries more logically, make them easier to understand, and even optimize performance by avoiding redundancy in complex joins or aggregations.

Common Table Expressions (CTEs) for Cleaner Queries

Benefits of CTEs

  • Improved Readability: Breaking down complex queries makes them easier to understand.
  • Eliminate Redundancy: Define a result set once and reuse it multiple times.
  • Modular Design: Allows you to split queries into smaller parts and focus on specific logic at each step.
  • Self-Referencing (Recursive CTEs): Some CTEs allow for recursive functionality, ideal for hierarchical data.

Basic Syntax of a CTE

The basic syntax for creating a CTE is as follows:

WITH cte_name AS (
    SELECT column1, column2, ...
    FROM table_name
    WHERE condition
)
SELECT column1, column2, ...
FROM cte_name;
  • WITH: This keyword initiates the CTE.
  • cte_name: The name given to the CTE, which can be referenced later in the main query.
  • CTE Body: Contains the subquery that forms the virtual table.
  • Main Query: References the CTE by name.

Example 1: Using a Simple CTE for Clarity

Let's say you want to find the average order amount per customer from a large orders table, then filter customers who exceed that average.

WITH AverageOrder AS (
    SELECT customer_id, AVG(order_amount) AS avg_order_amount
    FROM orders
    GROUP BY customer_id
)
SELECT customer_id
FROM AverageOrder
WHERE avg_order_amount > (SELECT AVG(order_amount) FROM orders);

In this example:

  • The CTE AverageOrder computes the average order amount per customer.
  • The main query then filters out customers whose average is above the global average.

Example 2: Chaining Multiple CTEs

CTEs can also be chained to break down complex operations. For example, let’s say you need to:

  • Identify top products.
  • Calculate total sales for those top products.
  • Rank the results by total sales.
WITH TopProducts AS (
    SELECT product_id, SUM(quantity) AS total_quantity
    FROM sales
    GROUP BY product_id
    HAVING SUM(quantity) > 100
),
ProductSales AS (
    SELECT product_id, total_quantity, price * total_quantity AS total_sales
    FROM TopProducts
    JOIN products ON TopProducts.product_id = products.product_id
)
SELECT product_id, total_sales,
       RANK() OVER (ORDER BY total_sales DESC) AS sales_rank
FROM ProductSales;

Here:

  • TopProducts identifies the products with more than 100 units sold.
  • ProductSales calculates total sales for those products.
  • The main query ranks these products by their sales, allowing for easy interpretation.

Recursive CTEs: Handling Hierarchical Data

A Recursive CTE is especially useful for querying hierarchical data, like organizational structures or parent-child relationships in a family tree.

Example 3: Finding All Subordinates of an Employee

Let’s consider a simple employee hierarchy with columns employee_id, manager_id, and name. The goal is to find all subordinates of a specific manager.

WITH RECURSIVE EmployeeHierarchy AS (
    SELECT employee_id, name, manager_id
    FROM employees
    WHERE manager_id = 1  -- Assuming manager_id 1 is the starting point
    UNION ALL
    SELECT e.employee_id, e.name, e.manager_id
    FROM employees e
    INNER JOIN EmployeeHierarchy eh ON e.manager_id = eh.employee_id
)
SELECT * FROM EmployeeHierarchy;

In this example:

  • The base query retrieves employees reporting directly to the manager with manager_id = 1.
  • The recursive part finds employees reporting to those direct reports, continuing until the entire hierarchy is retrieved.
  • Note: Recursive CTEs should be used carefully, as they can lead to performance issues with very large datasets.

CTEs vs. Subqueries: When to Use Each

While subqueries and CTEs can often be used interchangeably, CTEs are generally preferred in the following cases:

  • Readability: CTEs improve the readability of complex queries.
  • Recursion: Only CTEs can be recursive, which is essential for hierarchical data.
  • Reusable Logic: CTEs allow defining logic once and reusing it multiple times.

Best Practices for Using CTEs

  • Use Descriptive Names: Use names that describe the purpose of each CTE.
  • Avoid Excessive Nesting: Too many nested CTEs can make queries harder to read.
  • Optimize by Testing: Test performance, as some CTEs can cause inefficiencies with very large datasets.
  • Use Recursive CTEs Wisely: Recursive queries can be powerful but should be used with caution.

Advanced Example: Combining CTEs with Window Functions

Window functions, like ROW_NUMBER() or RANK(), are often used alongside CTEs to perform complex analyses.

Example 4: Ranking Employees by Sales within Departments

Let's create a CTE to rank employees based on their sales within their respective departments.

WITH DepartmentSales AS (
    SELECT department_id, employee_id, SUM(sales) AS total_sales
    FROM sales
    GROUP BY department_id, employee_id
),
RankedSales AS (
    SELECT department_id, employee_id, total_sales,
           RANK() OVER (PARTITION BY department_id ORDER BY total_sales DESC) AS sales_rank
    FROM DepartmentSales
)
SELECT * FROM RankedSales WHERE sales_rank = 1;

In this example:

  • DepartmentSales calculates total sales for each employee within each department.
  • RankedSales ranks employees by sales within each department.
  • The main query selects the top salesperson in each department, showcasing a clean and modular approach.

Conclusion

Common Table Expressions (CTEs) are a powerful tool for SQL query optimization, clarity, and performance. By using CTEs, you can break down complex queries, improve readability, and manage hierarchical data efficiently. As databases continue to optimize for CTEs, understanding and implementing them will help you write cleaner and more effective SQL queries.

Happy Querying! CTEs are a great way to keep your SQL code organized and efficient. Whether for one-off analysis or large-scale reporting, try integrating CTEs in your next project for cleaner, modular queries.

Checkout our dedicated servers India, Instant KVM VPS, and Web Hosting India