CTEs / WITH

Define temporary result sets that can be referenced within a SELECT, INSERT, UPDATE, or DELETE statement.

They work almost like subqueries.

Although, subqueries are typically used within the context of a larger query and are not reusable, whereas CTEs are reusable within the same query.

💡
CTEs: saves a result set to be reused within the same query. Subqueries: A query nested within a larger query. Not reusable. View: A result set saved on your database to be reused. Temp Table: temporary results sets that are stored in the tempdp database. Can be used outside a query but within the same session

CTES are defined by adding a WITH statement before SELECT, UPDATE, INSERT, DELETE, MERGE statements. The WITH clause can contain one or more CTEs separated by commas.

syntax:

WITH cte_name (column1, column2, ...) AS (
    -- SQL query that defines the CTE
)

WITH shows that its CTE query

cte_name: acts as the alias

Columns 1 and 2 are the columns to be derived which is an optional parameter. If ignored the CTE will inherit the columns from the query defining it.

The information after the AS is usually the subquery being executed

Example in code:

WITH EmployeeCTE AS (
    SELECT FirstName, LastName, Salary
    FROM Employees
    WHERE Department = 'IT'
)
SELECT FirstName, LastName
FROM EmployeeCTE
WHERE Salary > 50000;

Why CTES:

  1. Enables users to more easily write and maintain complex queries. Each CTE can have a clear, descriptive name that explains its purpose, making it easier for other developers (or your future self) to understand the intention behind each part of the query.

  2. Reduction of Redundancy: CTEs promote code reusability. When you have a complex operation that needs to be performed multiple times in a query, you can define it once in a CTE and then reference that CTE multiple times within the same query.

  3. Help perform multi-level aggregations. For instance, you might need to calculate monthly totals, and then aggregate those into quarterly or annual totals. CTEs allow you to create distinct levels of aggregation and build upon previous results in a structured and readable manner.

    Example:

     -- Create a CTE for daily sales
     WITH DailySales AS (
         SELECT
             DATE_TRUNC('day', sale_date) AS sale_day,
             SUM(sale_amount) AS daily_total
         FROM
             Sales
         GROUP BY
             DATE_TRUNC('day', sale_date)
     ),
    
     -- Create a CTE for monthly sales using the DailySales CTE
     MonthlySales AS (
         SELECT
             DATE_TRUNC('month', sale_day) AS sale_month,
             SUM(daily_total) AS monthly_total
         FROM
             DailySales
         GROUP BY
             DATE_TRUNC('month', sale_day)
     ),
    
     -- Create a CTE for yearly sales using the MonthlySales CTE
     YearlySales AS (
         SELECT
             DATE_TRUNC('year', sale_month) AS sale_year,
             SUM(monthly_total) AS yearly_total
         FROM
             MonthlySales
         GROUP BY
             DATE_TRUNC('year', sale_month)
     )
    
     -- Query the YearlySales CTE to get the final result
     SELECT
         EXTRACT(YEAR FROM sale_year) AS year,
         yearly_total
     FROM
         YearlySales
     ORDER BY
         year;
    

    In the above query, we are trying to classify the sales either as daily, monthly or yearly sales.

  4. Make code easier to debug and more visually appealing.

Types of CTEs

There are two types:

  1. Recursive CTE: references itself. By doing so, the CTE repeatedly executes and returns subsets of information

    Used when working with hierarchical data, such as organizational charts or tree-like structures.

     WITH RecursiveCTE (n) AS (
         SELECT 1
         UNION ALL
         SELECT n + 1
         FROM RecursiveCTE
         WHERE n < 10
     )
     SELECT n FROM RecursiveCTE;
    
    1. Nonrecursive CTEs:

used for general purposes and don't reference themselves.

    WITH EmployeeCTE AS (
        SELECT FirstName, LastName, Salary
        FROM Employees
        WHERE Department = 'IT'
    )
    SELECT FirstName, LastName
    FROM EmployeeCTE
    WHERE Salary > 50000;