LATERAL join for correlated subqueries in PostgreSQL - Time & Space Complexity
We want to understand how the time needed to run a LATERAL join with a correlated subquery changes as the data grows.
Specifically, how does the number of rows in the main table affect the total work done?
Analyze the time complexity of the following code snippet.
SELECT a.id, b.val
FROM a
JOIN LATERAL (
SELECT val
FROM b
WHERE b.a_id = a.id
ORDER BY val DESC
LIMIT 1
) b ON true;
This query finds, for each row in table a, the highest val from table b where b.a_id matches a.id.
Identify the loops, recursion, array traversals that repeat.
- Primary operation: For each row in
a, run a subquery onbto find matching rows. - How many times: The subquery runs once per row in
a.
As the number of rows in a grows, the subquery runs more times, each searching b for matches.
| Input Size (rows in a) | Approx. Operations |
|---|---|
| 10 | 10 subqueries on b |
| 100 | 100 subqueries on b |
| 1000 | 1000 subqueries on b |
Pattern observation: The total work grows roughly in direct proportion to the number of rows in a.
Time Complexity: O(n)
This means the time to run the query grows linearly with the number of rows in the main table a.
[X] Wrong: "The subquery runs just once, so the query is always fast regardless of data size."
[OK] Correct: The subquery runs once for each row in a, so if a has many rows, the total work adds up and takes longer.
Understanding how correlated subqueries and LATERAL joins scale helps you write efficient queries and explain your reasoning clearly in interviews.
"What if the subquery inside the LATERAL join returned multiple rows instead of just one? How would that affect the time complexity?"
Practice
LATERAL join in PostgreSQL?Solution
Step 1: Understand what LATERAL join does
A LATERAL join allows a subquery to use columns from the current row of the main query.Step 2: Identify the main purpose
This means the subquery runs once per row, using that row's data to produce related results.Final Answer:
To run a subquery for each row of the main table using that row's data -> Option AQuick Check:
LATERAL join = per-row subquery execution [OK]
- Thinking LATERAL joins all rows at once
- Confusing LATERAL with regular JOIN
- Assuming LATERAL speeds up queries by caching
Solution
Step 1: Identify correct LATERAL syntax
LATERAL must be explicitly stated before the subquery or table function to allow referencing outer query columns.Step 2: Check each option
SELECT a.id, b.value FROM table_a a JOIN LATERAL (SELECT value FROM table_b WHERE b.a_id = a.id) b ON true; correctly uses JOIN LATERAL with a subquery referencing outer alias 'a'. SELECT a.id, b.value FROM table_a a JOIN (SELECT value FROM table_b WHERE b.a_id = a.id) b ON true; misses LATERAL keyword. SELECT a.id, b.value FROM table_a a LEFT JOIN LATERAL table_b b ON b.a_id = a.id; uses LATERAL but tries to join a table directly without subquery syntax. SELECT a.id, b.value FROM table_a a CROSS JOIN table_b b WHERE b.a_id = a.id; uses CROSS JOIN without LATERAL, so no correlation.Final Answer:
SELECT a.id, b.value FROM table_a a JOIN LATERAL (SELECT value FROM table_b WHERE b.a_id = a.id) b ON true; -> Option BQuick Check:
Correct LATERAL syntax includes keyword before subquery [OK]
- Omitting LATERAL keyword before subquery
- Using regular JOIN without correlation
- Trying to join tables directly without subquery
users(id, name)orders(id, user_id, amount)What will this query return?
SELECT u.name, o.amount FROM users u JOIN LATERAL (SELECT amount FROM orders WHERE user_id = u.id ORDER BY amount DESC LIMIT 1) o ON true;
Solution
Step 1: Understand the LATERAL subquery
The subquery selects the highest order amount per user by ordering descending and limiting to 1.Step 2: Analyze the JOIN LATERAL effect
For each user row, the subquery runs and returns that user's top order amount, joining on true to include all users with orders.Final Answer:
Each user with their highest order amount -> Option DQuick Check:
LATERAL subquery returns top order per user [OK]
- Thinking it returns all orders per user
- Assuming syntax error due to ON true
- Confusing join with CROSS JOIN
SELECT u.name, o.amount FROM users u JOIN LATERAL (SELECT amount FROM orders WHERE user_id = u.id ORDER BY amount DESC LIMIT 1) o;
Solution
Step 1: Check JOIN syntax
Every JOIN requires an ON clause or USING clause to specify join condition.Step 2: Verify the query
The query uses JOIN LATERAL but omits ON clause, causing syntax error.Final Answer:
Missing ON clause after LATERAL join -> Option CQuick Check:
JOIN must have ON or USING clause [OK]
- Forgetting ON clause after JOIN LATERAL
- Thinking subquery can't use outer columns
- Believing ORDER BY is disallowed in subqueries
Solution
Step 1: Understand the requirement
We want two most recent orders per customer, so order by order_date DESC and limit 2 in subquery.Step 2: Check each option
SELECT c.name, o.order_id FROM customers c JOIN LATERAL (SELECT order_id FROM orders WHERE customer_id = c.id ORDER BY order_date DESC LIMIT 2) o ON true; uses JOIN LATERAL with correct ordering and limit, joining on true to include all customers with orders. SELECT c.name, o.order_id FROM customers c JOIN orders o ON o.customer_id = c.id ORDER BY o.order_date DESC LIMIT 2; limits total rows, not per customer. SELECT c.name, o.order_id FROM customers c CROSS JOIN LATERAL orders o WHERE o.customer_id = c.id LIMIT 2; uses CROSS JOIN LATERAL incorrectly without subquery. SELECT c.name, o.order_id FROM customers c LEFT JOIN LATERAL (SELECT order_id FROM orders WHERE customer_id = c.id ORDER BY order_date ASC LIMIT 2) o ON true; orders ASC, which gives oldest orders, not recent.Final Answer:
SELECT c.name, o.order_id FROM customers c JOIN LATERAL (SELECT order_id FROM orders WHERE customer_id = c.id ORDER BY order_date DESC LIMIT 2) o ON true; -> Option AQuick Check:
LATERAL with ORDER BY DESC and LIMIT 2 per customer [OK]
- Limiting total rows instead of per customer
- Ordering ascending instead of descending
- Using CROSS JOIN LATERAL without subquery
Ready to prove it?
Pass the test and this pattern moves unknown
