MySQL Subqueries
Also Known As
SQL subquery
correlated subquery MySQL
derived table
EXISTS vs IN MySQL
TL;DR
A query nested inside another — useful for filtering and deriving values, but correlated subqueries re-execute per outer row and can be O(n²).
Explanation
Subqueries can appear in SELECT (scalar), FROM (derived table), WHERE, or HAVING. Correlated subqueries reference the outer query and re-execute for each outer row — potentially catastrophically slow on large tables. Non-correlated subqueries execute once. IN (subquery) evaluates the full subquery; EXISTS short-circuits on first match — EXISTS is usually faster for large sets. MySQL 8+ improves subquery decorrelation automatically in some cases.
Common Misconception
✗ IN (SELECT ...) and EXISTS (SELECT ...) are always equivalent. EXISTS short-circuits; IN evaluates everything. On large subqueries EXISTS is significantly faster.
Why It Matters
A correlated subquery in a WHERE or SELECT clause is the SQL equivalent of the N+1 problem — executing thousands of times on large datasets and turning a fast query into a slow one.
Common Mistakes
- Using a correlated subquery in SELECT to fetch a related value — re-executes for every row.
- Using IN with a large subquery instead of a JOIN or EXISTS.
- Nesting subqueries more than two levels deep — readability and optimisability both suffer.
Avoid When
- Avoid correlated subqueries in SELECT — replace with a JOIN.
- Avoid deep nesting — flatten with CTEs (WITH clause) in MySQL 8 for readability.
When To Use
- Use EXISTS for semi-join checks — checking whether any related row exists.
- Use derived tables in FROM for complex aggregations that need to be joined back.
Code Examples
✗ Vulnerable
-- Correlated: re-runs for every user row → O(n²)
SELECT email,
(SELECT COUNT(*) FROM orders o WHERE o.user_id = u.id) AS cnt
FROM users u;
✓ Fixed
-- EXISTS short-circuits on first match
SELECT * FROM users u
WHERE EXISTS (SELECT 1 FROM orders o WHERE o.user_id = u.id AND o.status = 'paid');
-- Derived table in FROM (executes once)
SELECT u.email, stats.total
FROM users u
JOIN (SELECT user_id, SUM(total) AS total FROM orders GROUP BY user_id) stats
ON stats.user_id = u.id;
Tags
🤝 Adopt this term
£79/year · your link shown here
Added
31 Mar 2026
Views
14
🤖 AI Guestbook educational data only
|
|
Last 30 days
Agents 0
No pings yet today
No pings yesterday
Perplexity 4
Google 4
Unknown AI 3
ChatGPT 1
Ahrefs 1
Also referenced
How they use it
crawler 10
crawler_json 2
pre-tracking 1
Related categories
⚡
DEV INTEL
Tools & Severity
⚙ Fix effort: Medium
⚡ Quick Fix
Replace correlated subqueries with a JOIN — use EXISTS instead of IN for large subquery result sets
📦 Applies To
web
cli
🔗 Prerequisites
🔍 Detection Hints
SELECT (SELECT ... WHERE outer.id = ...) or WHERE col IN (SELECT ...)
Auto-detectable:
✗ No
⚠ Related Problems
🤖 AI Agent
Confidence: Medium
False Positives: Medium
✗ Manual fix
Fix: Medium
Context: Line
Tests: Update