Subqueries for temp results

Concepts covered: Scalar Subquery

Before CTEs became standard, all intermediate results had to be written inline as nested queries: a query inside another query's WHERE clause or FROM clause. Subqueries still appear constantly in production SQL and in interview questions, and they produce exactly the same results as an equivalent CTE. Understanding subqueries means you can read any SQL in the wild, write the embedded style when a full CTE block would be overkill, and recognize when a nested query is slowing things down enough to warrant refactoring.

Where subqueries go

Subqueries appear in 3 positions: inside WHERE, inside FROM, and inside SELECT.

Table subquery

SELECT *
FROM orders
WHERE total > (SELECT AVG(total) FROM orders)
orders
order_idcustomertotal
O1Alice200
O2Bob350
O3Carol180
O4Dave400
O5Eve170

A table subquery replaces a table name in the FROM clause with a complete SELECT statement. The inner query runs first to produce an intermediate result, then the outer query filters from that result.

Scalar subquery

A scalar subquery returns a single value that can be used in the SELECT list or WHERE clause.

SELECT id, (SELECT val FROM config WHERE config_key = 'default') AS default_val FROM logs;
config
config_keyval
defaultstandard
timeout30
retries3
logs
idmessagelevel
101User logininfo
102Page viewedinfo
103File uploadedinfo
Result
iddefault_val
101standard
102standard
103standard

The subquery retrieves a single value. Each row in the outer query shows the same default name.

Multi-row subquery

When a subquery returns multiple rows, use IN to check if a value matches any result.

SELECT id FROM orders WHERE user_id IN (SELECT id FROM users WHERE active = 1);
users
idnameactive
1Ana1
2Ben0
3Chloe1
4Dev1
5Elena0
orders
iduser_idtotal
1001125
1002240
1005315.5
1006560
1008432
1010218
1012122.5
Result
id
1001
1005
1008
1012

The subquery lists all active user IDs. The outer query selects orders linked to those users.

Practice: subquery filter

Example: devices over an error threshold

Suppose you need to monitor which devices have many error events. First, count the number of errors per device, then select devices exceeding a certain threshold.

Subqueries allow you to separate counting from selection, so each step is easy to check. You can reuse this approach for any dataset where part of the data must be computed before filtering.

Do
  • Use subqueries for one-off filtering or transformations
  • Test subquery performance separately before nesting
  • Keep subqueries simple and focused on a single task
Avoid
  • Avoid deeply nested subqueries; use CTEs for readability
  • Don't use subqueries when a simple join would suffice

Correlation decides how a subquery runs

The database processes a subquery differently depending on whether it references the outer query, and that changes how fast the query runs.

Correlated vs. uncorrelated

A non-correlated subquery runs once and shares its result with every row in the outer query. A correlated subquery references the outer query's current row, so it must re-execute for each row the outer query processes. On a table with 1 million rows, a correlated subquery runs 1 million times. This is why correlated subqueries can be slow and are often worth rewriting as a JOIN or CTE.

About this lesson

This section is part of the Query Structure: Intermediate lesson on DataDriven, the home of data engineering. Lessons cover SQL, Python, Spark, data modeling and pipeline architecture, and each section pairs its explanation with worked examples and practice you run as you go.