top of page

Welcome
to NumpyNinja Blogs

NumpyNinja: Blogs. Demystifying Tech,

One Blog at a Time.
Millions of views. 

CTE vs. Subqueries : A Guide to Choosing the Right Approach

Writing a complex SQL query often involves using the result of one query inside another. This leads to the eternal SQL dilemma: Should you use a nested subquery or define a CTE (Common Table Expression)? The answer is always query-specific, but understanding the strengths of each tool is crucial for writing efficient and maintainable code.

 

Let’s start with some simple examples to differentiate the two:

A subquery is a query nested inside another SQL statement. It’s more or less like a footnote – defined right where it’s used.

SELECT patient_id, glucose_value

FROM glucose_monitor

WHERE glucose_value > (SELECT AVG(glucose_value) FROM glucose_monitor);

 

A Common Table Expression or CTE is a temporary, named result that starts with the WITH clause and is defined before the main query. Think of it as a chapter outline that is defined upfront and then referenced by its name.

WITH high_glucose_patients AS (

SELECT patient_id FROM dexcom WHERE glucose_value > 180

)

SELECT * FROM high_glucose_patients

WHERE patient_id IN (SELECT patient_id FROM patients WHERE age > 60);

 

Now for the real question: When to Use Which?

 

Choose CTEs When:

1. Your query has multiple logical steps and you need it to be readable and organized.

2. You need the same result set multiple times.

3. When you are working with hierarchical data like organizational charts, categories, etc.

 

Choose Subqueries When:

1. You need a single value for a WHERE or SELECT clause.

2. You are checking for the existence of a certain type of data using EXISTS/ NOT EXISTS.

3. You need row by row calculations specific to each record.

 

Let’s take a closer look at a certain use case to understand the differences better.

Consider a food alert system for diabetic patients where you need to write a query to identity problematic food intake patterns:

 

The CTE Approach:

 

WITH patient_averages AS (

SELECT patient_id, AVG(calorie) AS avg_daily_calories

FROM daily_totals GROUP BY patient_id

),

ranked_foods AS (

 SELECT f.patient_id, f.logged_food, f.calorie,

 ROW_NUMBER() OVER (PARTITION BY f.patient_id ORDER BY f.calorie DESC) AS rank

 FROM foodlog f

 JOIN patient_averages p ON f.patient_id = p.patient_id

)

SELECT patient_id, logged_food, calorie

FROM ranked_foods

WHERE rank = 1 AND calorie > 500;

 

The subquery Approach:


SELECT patient_id, logged_food, calorie

FROM (

SELECT f.patient_id, f.logged_food, f.calorie,

ROW_NUMBER() OVER (PARTITION BY f.patient_id ORDER BY f.calorie DESC) AS rank

FROM foodlog f

JOIN (

SELECT patient_id, AVG(calorie) AS avg_daily_calories

FROM (SELECT patient_id, SUM(calorie) AS calorie FROM foodlog GROUP BY patient_id, DATE(time_of_intake)) daily_totals

GROUP BY patient_id

) pa ON f.patient_id = pa.patient_id

) ranked_foods

WHERE rank = 1 AND calorie > 500;

 

The CTE approach is not just easier to read; it's often more efficient. The patient_averages CTE calculates the averages once and reuses the result. In the subquery approach, the database might need to recalculate the average for every single food log entry. While this might be acceptable for a small hobby database, it will become significantly slower in a large organizational database with terabytes of data. The CTE's logical separation makes the query easier to debug, modify, and maintain.

 

The Bottom Line:


For simple, single-use values and EXISTS checks, a subquery is often the right tool. However, for multi-step analytical queries where readability and performance matter, default to using a CTE. It transforms a nested mess into a clean, logical story, making your code far more robust for the future.

 

 

 
 

+1 (302) 200-8320

NumPy_Ninja_Logo (1).png

Numpy Ninja Inc. 8 The Grn Ste A Dover, DE 19901

© Copyright 2025 by Numpy Ninja Inc.

  • Twitter
  • LinkedIn
bottom of page