Mastering Apache Spark SQL: Create Complex Queries with Common Table Expressions CTE (WITH Clause)

Islam ElbannaIslam Elbanna
5 min read

Apache Spark SQL uses SQL capabilities to process large-scale structured data. One powerful feature in modern SQL is the WITH clause, supported in Spark SQL as Common Table Expressions (CTE). CTE offer a more organized, readable, and often more efficient way to build complex queries. This article will explain what CTE is, why it is valuable in Spark SQL, and explore its syntax with practical examples.

What is a Common Table Expression (CTE)?

A Common Table Expression, or CTE, is a named, temporary result set that you define within a single SQL statement. It's like a temporary, virtual table that only exists while the query is running. A CTE starts with the WITH clause, followed by one or more named sub-queries.

The basic syntax example is:

WITH expression_name [ ( column_name [ , ... ] ) ] [ AS ] ( query ) [ , ... ]
  • expression_name: A unique name you assign to your temporary result set.

  • (column_name, ...): An optional list of column aliases for the CTE's output. If not provided, Spark SQL will infer column names from the SELECT statement within the CTE.

  • AS (query): The SELECT statement that defines the logic for your CTE.

Why Use CTE in Spark SQL?

While you can often achieve similar results using nested sub-queries, CTE brings several significant advantages to Spark SQL development:

  1. Improves readability: Complex queries can quickly become difficult to follow and modify due to nested sub-queries. CTEs let you break down common logic into smaller, named, and more manageable parts. Each CTE acts as a logical unit of work, making the entire query easier to understand, debug, and maintain.

  2. Enhances usability: A key benefit of CTEs is that you can reference them multiple times within the same WITH clause or the final SELECT statement. This helps avoid code duplication and ensures consistency in your intermediate calculations.

  3. Simplifies debugging: By breaking down the logic into separate blocks, you can easily debug each part of the CTE independently. This helps you find issues much faster than trying to debug a single, complex query.

  4. Potential for Optimization: While CTEs are defined as temporary result sets, Spark often treats them like logical views. This allows Spark's Catalyst Optimizer to apply optimizations, such as pushing down predicates, across CTE boundaries. This can result in more efficient execution plans, particularly when a CTE is used multiple times. Spark might materialize the result or optimize its execution just once.

Practical example

Suppose we have a sales table and want to find the total sales for each product category.

-- Sample Data Setup (for demonstration purposes)
-- This would typically be a pre-existing table or DataFrame
CREATE OR REPLACE TEMPORARY VIEW sales AS
SELECT * FROM VALUES
    ('Electronics', 'Laptop', 1200.00, '2024-01-15'),
    ('Electronics', 'Mouse', 25.00, '2024-01-15'),
    ('Clothing', 'T-Shirt', 20.00, '2024-01-16'),
    ('Electronics', 'Keyboard', 75.00, '2024-01-16'),
    ('Clothing', 'Jeans', 50.00, '2024-01-17'),
    ('Electronics', 'Monitor', 300.00, '2024-01-17')
AS sales_data(category, product, amount, sale_date);

-- Using a CTE to calculate total sales per category
WITH CategorySales AS (
    SELECT category, SUM(amount) AS total_category_sales
    FROM sales
    GROUP BY category
)
SELECT category, total_category_sales
FROM CategorySales
ORDER BY total_category_sales DESC;

Electronics    1600.00
Clothing    70.00
Time taken: 0.157 seconds, Fetched 2 row(s)

In this example, CategorySales is our CTE. It calculates the sum of the amount grouped by category. The final SELECT statement then simply queries this temporary CategorySales result set.

Chaining CTEs

One of the most powerful features of CTEs is the ability to chain them. This means a later CTE can refer to an earlier CTE within the same WITH clause. This approach lets you build complex logic step by step.

Consider extending the previous example to find the average sales across all categories and then identify categories whose sales are above this average.

WITH CategorySales AS (
    SELECT category, SUM(amount) AS total_category_sales
    FROM sales
    GROUP BY category
), AverageOverallSales AS (
    SELECT AVG(total_category_sales) AS overall_avg_sales
    FROM CategorySales -- Referencing the first CTE
)
SELECT
    cs.category,
    cs.total_category_sales,
    aos.overall_avg_sales
FROM CategorySales cs
CROSS JOIN AverageOverallSales aos
WHERE cs.total_category_sales > aos.overall_avg_sales
ORDER BY cs.total_category_sales DESC;

Electronics    1600.00    835.000000
Time taken: 0.321 seconds, Fetched 1 row(s)

Here, CategorySales calculates the total sales for each category. Then, AverageOverallSales uses CategorySales to find the overall average. Finally, the main query joins these two CTEs to filter out categories with sales above the average.

Best fit use cases

CTEs are highly beneficial in various real-world scenarios:

  • Step-by-Step data transformation: When you need to apply a series of transformations like filtering, aggregation, and joining to your data, CTEs let you define each step clearly.

  • Complex aggregations and analytics: For multi-level aggregations or calculations involving window functions where intermediate results are needed, CTEs offer a clear structure.

  • Sub-query factorization: If you find yourself writing the same sub-query multiple times, extract it into a CTE for usability.

  • Anomaly detection and quality checks: You can define CTEs to spot anomalies or specific data patterns and then use these CTEs in your main query to flag or exclude problematic records.

  • Improving Performance for Repeated Computations: If a complex sub-query is calculated multiple times in a large query, turning it into a CTE can sometimes help Spark optimize its execution, potentially avoiding repeated calculations.

Conclusion

Common Table Expressions are a key feature in modern SQL that greatly improve the developer experience. By allowing modularity, enhancing readability, and promoting re-usability, CTEs help data professionals write cleaner, more maintainable, and often more efficient Spark SQL queries. They turn complex data challenges into clear, manageable steps.

5
Subscribe to my newsletter

Read articles from Islam Elbanna directly inside your inbox. Subscribe to the newsletter, and don't miss out.

Written by

Islam Elbanna
Islam Elbanna

I am a software engineer with over 12 years of experience in the IT industry, including 4+ years specializing in big data technologies such as Hadoop, Sqoop, Spark, and more, along with a foundation in machine learning. With 7+ years in software engineering, I have extensive experience in web development, utilizing Java, HTML, Bootstrap, Angular, and various frameworks to build and deploy high-scale distributed systems. Additionally, I possess DevOps skills, with hands-on experience managing AWS cloud infrastructure and Linux systems.