**Approach (brief)**Replace the correlated subquery with an aggregate over orders joined to users (or a window/CTE) so the database can hash/merge join and aggregate once instead of running a subquery per user.**Rewritten query (performant)**sql
-- Using LEFT JOIN + GROUP BY
SELECT u.id,
COALESCE(counts.recent_orders, 0) AS recent_orders
FROM users u
LEFT JOIN (
SELECT o.user_id, COUNT(*) AS recent_orders
FROM orders o
WHERE o.created_at > now() - interval '30 days'
GROUP BY o.user_id
) counts ON counts.user_id = u.id;
Alternative with window function if you need ordering or more columns:sql
SELECT DISTINCT u.id,
COALESCE(o_cnt.recent_orders, 0) AS recent_orders
FROM users u
LEFT JOIN (
SELECT user_id, COUNT(*) OVER (PARTITION BY user_id) AS recent_orders
FROM orders
WHERE created_at > now() - interval '30 days'
) o_cnt ON o_cnt.user_id = u.id;
**Why this is faster**- Correlated subqueries run per row; cost is O(users * cost_of_index_scan). The join+aggregation scans orders once, groups by user_id, then joins — far fewer repeated operations.- The DB can use a single sequential/ index scan and an efficient hash or sort-aggregate.**Indexes to add**- Composite index on orders(created_at, user_id) or on (user_id, created_at). For this WHERE plus GROUP BY pattern: - Preferred: CREATE INDEX idx_orders_created_at_user_id ON orders (created_at DESC, user_id); - Or: CREATE INDEX idx_orders_user_id_created_at ON orders (user_id, created_at DESC);- Consider partial index for 30-day window if queries always filter recent range: - CREATE INDEX idx_orders_recent_user ON orders (user_id) WHERE created_at > now() - interval '30 days';**Complexity & notes**- Time: O(N_orders + N_users) vs O(N_users * log N_orders) for correlated.- Edge cases: large result sets — ensure appropriate memory for hash aggregates; tune work_mem or use GROUP BY sort-aggregate.