SQL Practice Logo

SQLPractice Online

CTE Performance Optimization: Concept

Module: Subqueries & CTEs

CTE performance depends on three factors: (1) Materialization - does the CTE execute once or multiple times? (2) Filtering - does the CTE scan the entire table or just needed rows? (3) Indexing - can the database use indexes or must it do full table scans? A slow CTE query usually has one or more of these issues. The fix: check execution plan with EXPLAIN ANALYZE, identify the bottleneck, apply the right optimization. Understanding these three factors prevents 90% of CTE performance problems.

**

**1. Materialization: Execute Once or Multiple Times?**

PostgreSQL 12+ defaults to NOT MATERIALIZED (inlining). If CTE is referenced multiple times, it executes multiple times.

```sql

-- Problem: CTE executes 3 times (45 seconds)

WITH monthly_sales AS (

SELECT month, SUM(amount) as total

FROM sales -- 50M rows

GROUP BY month

)

SELECT * FROM monthly_sales WHERE total > 100000

UNION ALL

SELECT * FROM monthly_sales WHERE total <= 100000

UNION ALL

SELECT * FROM monthly_sales WHERE month = '2024-01';

-- Scans 50M rows 3 times = 150M rows

-- Solution: Add MATERIALIZED hint (8 seconds)

WITH monthly_sales AS MATERIALIZED (

SELECT month, SUM(amount) as total

FROM sales -- 50M rows

GROUP BY month

)

SELECT * FROM monthly_sales WHERE total > 100000

UNION ALL

SELECT * FROM monthly_sales WHERE total <= 100000

UNION ALL

SELECT * FROM monthly_sales WHERE month = '2024-01';

-- Scans 50M rows once, reuses results

```

**How to check:** Use EXPLAIN ANALYZE

```sql

EXPLAIN ANALYZE

WITH cte AS (SELECT ...)

SELECT * FROM cte;

-- Look for:

-- "CTE Scan on cte" = Materialized (good if reused)

-- Inlined subquery = Not materialized (bad if reused)

```

**2. Filtering: Scan Entire Table or Just Needed Rows?**