Subqueries in FROM (Derived Tables)
Derived Tables Must Have an Alias
-- Pre-aggregate order totals per customer, then filter the aggregate SELECT customer_totals.customer_id, customer_totals.lifetime_value FROM ( SELECT customer_id, SUM(total) AS lifetime_value FROM orders GROUP BY customer_id ) AS customer_totals WHERE customer_totals.lifetime_value > 5000;
AS customer_totals in the example above is not optional styling. Omitting it produces a syntax error in PostgreSQL, MySQL, and SQL Server alike, because the parser needs a name to refer to the subquery's columns by. (The AS keyword itself is optional in most dialects — you can write FROM (SELECT ...) customer_totals — but the alias name is required.)Why Use a Derived Table
The most common reason to reach for a derived table is to pre-aggregate or pre-shape data before doing more work with it. Once data has been summarized in the subquery, the outer query can filter, join, or sort that summary as if it were an ordinary table — without repeating the aggregation logic or fighting with GROUP BY rules in a single giant query.
-- Join a derived table of monthly revenue against a targets table
SELECT
monthly_revenue.month,
monthly_revenue.revenue,
targets.target_revenue,
monthly_revenue.revenue - targets.target_revenue AS variance
FROM (
SELECT
DATE_TRUNC('month', order_date) AS month,
SUM(total) AS revenue
FROM orders
GROUP BY DATE_TRUNC('month', order_date)
) AS monthly_revenue
JOIN targets
ON targets.month = monthly_revenue.month
ORDER BY monthly_revenue.month;Notice that the derived table (monthly_revenue) already did the hard work of grouping and summing before the JOIN ever happens. The outer query is left with a much simpler job: join two already-shaped result sets together and compute a difference.
Filtering an Aggregate Further
A derived table is also useful when you need to filter on an aggregated value using something other than HAVING — for example, when the filter condition itself involves another calculation, or when you want to reuse the aggregated column multiple times without repeating the expression.
SELECT product_id, total_sold, total_sold * 0.1 AS bonus_threshold FROM ( SELECT product_id, SUM(quantity) AS total_sold FROM order_items GROUP BY product_id ) AS product_sales WHERE total_sold > 100;
WITH name AS (...) is usually the more readable alternative — it names the subquery before the main query starts, and multiple CTEs can be chained together cleanly instead of nesting parentheses inside FROM. See the CTEs with WITH page for the same pattern rewritten more readably.A subquery in FROM is called a derived table — it behaves exactly like a real table to the outer query.
An alias is required — write FROM (SELECT ...) AS alias_name, or you'll get a syntax error.
Derived tables are ideal for pre-aggregating or pre-filtering data before further joins, filters, or sorting.
For multiple layered subqueries, prefer CTEs (WITH) for readability over nested derived tables.